iricardoxd's picture
xd
fde6ce9
"""Handles authentication for TikTokUploader"""
from http import cookiejar
from time import time, sleep
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from tiktok_uploader import config, logger
from tiktok_uploader.browsers import get_browser
from tiktok_uploader.utils import green
class AuthBackend:
"""
Handles authentication for TikTokUploader
"""
username: str
password: str
cookies: list
def __init__(self, username: str = '', password: str = '',
cookies_list: list = None, cookies=None, cookies_str=None, sessionid: str = None):
"""
Creates the authenticaiton backend
Keyword arguments:
- username -> the accounts's username or email
- password -> the account's password
- cookies -> a list of cookie dictionaries of cookies which is Selenium-compatable
"""
if (username and not password) or (password and not username):
raise InsufficientAuth()
self.cookies = self.get_cookies(path=cookies) if cookies else []
self.cookies += self.get_cookies(cookies_str=cookies_str) if cookies_str else []
self.cookies += cookies_list if cookies_list else []
self.cookies += [{'name': 'sessionid', 'value': sessionid}] if sessionid else []
if not (self.cookies or (username and password)):
raise InsufficientAuth()
self.username = username
self.password = password
if cookies:
logger.debug(green("Authenticating browser with cookies"))
elif username and password:
logger.debug(green("Authenticating browser with username and password"))
elif sessionid:
logger.debug(green("Authenticating browser with sessionid"))
elif cookies_list:
logger.debug(green("Authenticating browser with cookies_list"))
def authenticate_agent(self, driver):
"""
Authenticates the agent using the browser backend
"""
# tries to use cookies
if not self.cookies and self.username and self.password:
self.cookies = login(driver, username=self.username, password=self.password)
logger.debug(green("Authenticating browser with cookies"))
driver.get(config['paths']['main'])
WebDriverWait(driver, config['explicit_wait']).until(EC.title_contains("TikTok"))
for cookie in self.cookies:
try:
driver.add_cookie(cookie)
except Exception as _:
logger.error('Failed to add cookie %s', cookie)
return driver
def get_cookies(self, path: str = None, cookies_str: str = None) -> dict:
"""
Gets cookies from the passed file using the netscape standard
"""
if path:
with open(path, "r", encoding="utf-8") as file:
lines = file.read().split("\n")
else:
lines = cookies_str.split("\n")
return_cookies = []
for line in lines:
split = line.split('\t')
if len(split) < 6:
continue
split = [x.strip() for x in split]
try:
split[4] = int(split[4])
except ValueError:
split[4] = None
return_cookies.append({
'name': split[5],
'value': split[6],
'domain': split[0],
'path': split[2],
})
if split[4]:
return_cookies[-1]['expiry'] = split[4]
return return_cookies
def login_accounts(driver=None, accounts=[(None, None)], *args, **kwargs) -> list:
"""
Authenticates the accounts using the browser backend and saves the required credentials
Keyword arguments:
- driver -> the webdriver to use
- accounts -> a list of tuples of the form (username, password)
"""
driver = driver or get_browser(headless=True, *args, **kwargs)
cookies = {}
for account in accounts:
username, password = get_username_and_password(account)
cookies[username] = login(driver, username, password)
return cookies
def login(driver, username: str, password: str):
"""
Logs in the user using the email and password
"""
assert username and password, "Username and password are required"
# checks if the browser is on TikTok
if not config['paths']['main'] in driver.current_url:
driver.get(config['paths']['main'])
# checks if the user is already logged in
if driver.get_cookie(config['selectors']['login']['cookie_of_interest']):
# clears the existing cookies
driver.delete_all_cookies()
# goes to the login site
driver.get(config['paths']['login'])
# selects and fills the login and the password
username_field = WebDriverWait(driver, config['explicit_wait']).until(
EC.presence_of_element_located((By.XPATH, config['selectors']['login']['username_field']))
)
username_field.clear()
username_field.send_keys(username)
password_field = driver.find_element(By.XPATH, config['selectors']['login']['password_field'])
password_field.clear()
password_field.send_keys(password)
# submits the form
submit = driver.find_element(By.XPATH, config['selectors']['login']['login_button'])
submit.click()
print(f'Complete the captcha for {username}')
# Wait until the session id cookie is set
start_time = time()
while not driver.get_cookie(config['selectors']['login']['cookie_of_interest']):
sleep(0.5)
if time() - start_time > config['explicit_wait']:
raise InsufficientAuth() # TODO: Make this something more real
# wait until the url changes
WebDriverWait(driver, config['explicit_wait']).until(EC.url_changes(config['paths']['login']))
return driver.get_cookies()
def get_username_and_password(login_info: tuple or dict):
"""
Parses the input into a username and password
"""
if not isinstance(login_info, dict):
return login_info[0], login_info[1]
# checks if they used email or username
if 'email' in login_info:
return login_info['email'], login_info['password']
elif 'username' in login_info:
return login_info['username'], login_info['password']
raise InsufficientAuth()
def save_cookies(path, cookies: list):
"""
Saves the cookies to a netscape file
"""
# saves the cookies to a file
cookie_jar = cookiejar.MozillaCookieJar(path)
cookie_jar.load()
for cookie in cookies:
cookie_jar.set_cookie(cookie)
cookie_jar.save()
class InsufficientAuth(Exception):
"""
Insufficient authentication:
> TikTok uses cookies to keep track of the user's authentication or session.
Either:
- Use a cookies file passed as the `cookies` argument
- easily obtained using https://github.com/kairi003/Get-cookies.txt-LOCALLY
- Use a cookies list passed as the `cookies_list` argument
- can be obtained from your browser's developer tools under storage -> cookies
- only the `sessionid` cookie is required
"""
def __init__(self, message=None):
super().__init__(message or self.__doc__)