# Contains functions having to do with logging in import urllib import json from youtube import common import re import settings import http.cookiejar import io import os try: with open(os.path.join(settings.data_dir, 'accounts.txt'), 'r', encoding='utf-8') as f: accounts = json.loads(f.read()) except FileNotFoundError: # global var for temporary storage of account info accounts = {} def save_accounts(): to_save = {username: account for username, account in accounts.items() if account['save']} with open(os.path.join(settings.data_dir, 'accounts.txt'), 'w', encoding='utf-8') as f: f.write(json.dumps(to_save)) def add_account(username, password, save): cookie_jar = http.cookiejar.LWPCookieJar() _login(username, password, cookie_jar) accounts[username] = { "save":save, "cookies":cookie_jar.as_lwp_str(ignore_discard=False, ignore_expires=False), } def cookie_jar_from_lwp_str(lwp_str): cookie_jar = http.cookiejar.LWPCookieJar() # HACK: cookiejar module insists on using filenames and reading files for you, # so present a StringIO to this internal method which takes a filelike object cookie_jar._really_load(self, io.StringIO(lwp_str), "", False, False) return cookie_jar def account_cookie_jar(username): return cookie_jar_from_lwp_str(accounts[username]['cookies']) def get_account_login_page(query_string): style = ''' main{ display: grid; grid-template-columns: minmax(0px, 3fr) 640px 40px 500px minmax(0px,2fr); } main form{ margin-top:20px; grid-column:2; display:grid; justify-items: start; align-content: start; grid-row-gap: 10px; } #username, #password{ grid-column:2; width: 250px; } #add-account-button{ margin-top:20px; } ''' page = '''
''' return common.yt_basic_template.substitute( page_title = "Login", style = style, header = common.get_header(), page = page, ) # --------------------------------- # Code ported from youtube-dl # --------------------------------- from html.parser import HTMLParser as compat_HTMLParser import http.client as compat_http_client class HTMLAttributeParser(compat_HTMLParser): """Trivial HTML parser to gather the attributes for a single element""" def __init__(self): self.attrs = {} compat_HTMLParser.__init__(self) def handle_starttag(self, tag, attrs): self.attrs = dict(attrs) def extract_attributes(html_element): """Given a string for an HTML element such as'
'(Note that only TOTP (Google Authenticator App) codes work at this time.)')
return False
tfa_code = remove_start(tfa_code, 'G-')
tfa_req = [
user_hash, None, 2, None,
[
9, None, None, None, None, None, None, None,
[None, tfa_code, True, 2]
]]
tfa_results = req(
_TFA_URL.format(tl), tfa_req,
'Submitting TFA code', 'Unable to submit TFA code')
if tfa_results is False:
return False
tfa_res = try_get(tfa_results, lambda x: x[0][5], list)
if tfa_res:
tfa_msg = try_get(tfa_res, lambda x: x[5], str)
warn(
'Unable to finish TFA: %s' % 'Invalid TFA code'
if tfa_msg == 'INCORRECT_ANSWER_ENTERED' else tfa_msg)
return False
check_cookie_url = try_get(
tfa_results, lambda x: x[0][-1][2], str)
else:
CHALLENGES = {
'LOGIN_CHALLENGE': "This device isn't recognized. For your security, Google wants to make sure it's really you.",
'USERNAME_RECOVERY': 'Please provide additional information to aid in the recovery process.',
'REAUTH': "There is something unusual about your activity. For your security, Google wants to make sure it's really you.",
}
challenge = CHALLENGES.get(
challenge_str,
'%s returned error %s.' % ('youtube', challenge_str))
warn('%s\nGo to https://accounts.google.com/, login and solve a challenge.' % challenge)
return False
else:
check_cookie_url = try_get(res, lambda x: x[2], str)
if not check_cookie_url:
warn('Unable to extract CheckCookie URL')
return False
try:
check_cookie_results = common.fetch_url(check_cookie_url, report_text="Checked cookie", cookie_jar_send=cookie_jar, cookie_jar_receive=cookie_jar).decode('utf-8')
except (urllib.error.URLError, compat_http_client.HTTPException, socket.error) as err:
return False
if 'https://myaccount.google.com/' not in check_cookie_results:
warn('Unable to log in')
return False
return True