diff --git a/.gitignore b/.gitignore index f559e48..65525b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ venv/ __pycache__/ .env -*.db \ No newline at end of file +*.db +cookies.txt \ No newline at end of file diff --git a/vrcapi.py b/vrcapi.py index 88a15be..5c4cbf0 100644 --- a/vrcapi.py +++ b/vrcapi.py @@ -1,16 +1,55 @@ import os import logging -import vrchatapi -from vrchatapi.api import authentication_api, groups_api +from vrchatapi import ApiClient as _ApiClient +from vrchatapi import GroupsApi, Configuration, AuthenticationApi +from http.cookiejar import LWPCookieJar from vrchatapi.exceptions import UnauthorizedException, ApiException from vrchatapi.models.two_factor_auth_code import TwoFactorAuthCode from vrchatapi.models.two_factor_email_code import TwoFactorEmailCode from totp import get_totp +# Modded ApiClient with cookie support +# Credit: Katsi on the VRC API Community Discord +class ApiClient(_ApiClient): + def save_cookies(self): + # validate env variable and create cookie jar + filename = os.getenv("COOKIE_FILE") + if filename is not None: + cookie_jar = LWPCookieJar(filename=filename) + else: + raise ValueError("COOKIE_FILE env variable not set") + # load cookies from API client into memory + for cookie in self.rest_client.cookie_jar: + cookie_jar.set_cookie(cookie) + + # save cookies + logging.info("Attempting to save cookies") + cookie_jar.save() + + def load_cookies(self): + # validate env variable and create cookie jar + filename = os.getenv("COOKIE_FILE") + if filename is not None: + cookie_jar = LWPCookieJar(filename=filename) + else: + raise ValueError("COOKIE_FILE env variable not set") + + # load existing cookies if possible, else create a new cookie jar + try: + logging.info("Attempting to load cookies") + cookie_jar.load() + except FileNotFoundError: + logging.info("Attempting to save cookies") + cookie_jar.save() + return + # transfer cookies from our session + for cookie in cookie_jar: + self.rest_client.cookie_jar.set_cookie(cookie) + def get_group_instances(): group_instances = [] - configuration = vrchatapi.Configuration( + configuration = Configuration( # Get username/password from env variables username = os.getenv("USER_NAME"), password = os.getenv("USER_PASSWORD"), @@ -18,57 +57,56 @@ def get_group_instances(): group_id = os.getenv("GROUP_ID") - with vrchatapi.ApiClient(configuration) as api_client: + with ApiClient(configuration) as api_client: # Set our User-Agent as per VRChat Usage Policy and call the Auth API api_client.user_agent = "GroupInstanceLogger/0.1alpha me@williamtpeebles.com" - auth_api = authentication_api.AuthenticationApi(api_client) + + # Attempt to load cookies + api_client.load_cookies() + + auth_api = AuthenticationApi(api_client) try: # Calling getCurrentUser on Authentication API logs you in if the user isn't already logged in. current_user = auth_api.get_current_user() + api_client.save_cookies() # Save cookies after successful login except UnauthorizedException as e: - handle_auth_exception(auth_api, e) - current_user = auth_api.get_current_user() + handle_auth_exception(auth_api, e) + current_user = auth_api.get_current_user() + api_client.save_cookies() # Save cookies after successful login - # Call groups APIs - groups_api_instance = vrchatapi.GroupsApi(api_client) - - try: - api_response = groups_api_instance.get_group_instances(group_id) # Get group - for instance in api_response: - group_instances.append(instance) - return group_instances - except ApiException as e: - print("Exception when calling GroupsApi->get_group: %s\n" % e) logging.info(f"Logged in as: {current_user.display_name}") - try: - api_response = groups_api.GroupsApi(api_client),get_group_instances(group_id) - group_instances.extend(api_response) - except ApiException as e: - logging.error(f"Exception when calling GroupsApi->get_group_instances: {e}") - except Exception as e: - logging.error(f"Unexpected Error: {e}") - return group_instances + # Call groups APIs + groups_api_instance = GroupsApi(api_client) - + try: + api_response = groups_api_instance.get_group_instances(group_id) # Get group instances + for instance in api_response: + group_instances.append(instance) + except ApiException as e: + logging.error(f"Exception when calling GroupsApi->get_group_instances: {e}") + except Exception as e: + logging.error(f"Unexpected Error: {e}") + + return group_instances def handle_auth_exception(auth_api, e): if e.status == 200: if "Email 2 Factor Authentication" in e.reason: - # Calling email verify2fa if the account has 2FA disabled, give warning that automatic runs cannot be done - logging.warning("IMPORTANT: Your VRChat Account is only enabled for **email** based 2-factor authentication! " - "You will not be able to automate runs of GroupInstanceLogger\n" - "Set up your account to use TOTP-based 2FA so the application can log you in automatically\n\n") - auth_api.verify2_fa_email_code(two_factor_email_code=TwoFactorEmailCode(input("Email 2FA Code: "))) + # Calling email verify2fa if the account has 2FA disabled, give warning that automatic runs cannot be done + logging.warning("IMPORTANT: Your VRChat Account is only enabled for **email** based 2-factor authentication! " + "You will not be able to automate runs of GroupInstanceLogger\n" + "Set up your account to use TOTP-based 2FA so the application can log you in automatically\n\n") + auth_api.verify2_fa_email_code(two_factor_email_code=TwoFactorEmailCode(input("Email 2FA Code: "))) elif "2 Factor Authentication" in e.reason: - # Check if TOTP code is defined in env variables - totp_key = os.getenv("TOTP_KEY") - if totp_key: - logging.info("Attempting to authenticate with defined TOTP key") - auth_api.verify2_fa(two_factor_auth_code=TwoFactorAuthCode(get_totp())) - else: - logging.info("No TOTP key defined, prompting manual input of 2FA code") - auth_api.verify2_fa(two_factor_auth_code=TwoFactorAuthCode(input("Enter 2FA Code: "))) + # Check if TOTP code is defined in env variables + totp_key = os.getenv("TOTP_KEY") + if totp_key: + logging.info("Attempting to authenticate with defined TOTP key") + auth_api.verify2_fa(two_factor_auth_code=TwoFactorAuthCode(get_totp())) + else: + logging.info("No TOTP key defined, prompting manual input of 2FA code") + auth_api.verify2_fa(two_factor_auth_code=TwoFactorAuthCode(input("Enter 2FA Code: "))) else: - logging.error(f"UnauthorizedException: {e}") \ No newline at end of file + logging.error(f"UnauthorizedException: {e}")