173 lines
6 KiB
Python
173 lines
6 KiB
Python
import time
|
||
import sys
|
||
import json
|
||
import logging
|
||
import pyscape
|
||
import pyscape.utils
|
||
import pyscape.ui
|
||
import pyscape.game
|
||
|
||
# Настройка логирования
|
||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s")
|
||
|
||
|
||
def launch_game_with_token(token, id_token):
|
||
"""
|
||
Запускает игру с переданным токеном и id_token.
|
||
"""
|
||
try:
|
||
id_token_payload = pyscape.parse_id_token(id_token)
|
||
except Exception as e:
|
||
raise e
|
||
|
||
# Получаем данные пользователя по sub из id_token
|
||
sub = id_token_payload["sub"]
|
||
user = pyscape.get_user_details(sub, token["access_token"])
|
||
logging.info("You are logged in as: {}#{}".format(user.display_name, user.suffix))
|
||
|
||
# Проверка поля login_provider в JWT
|
||
if (
|
||
"login_provider" not in id_token_payload
|
||
or id_token_payload["login_provider"] != pyscape.LOGIN_PROVIDER
|
||
):
|
||
logging.info(
|
||
"No known login_provider found in JWT token, using standard login..."
|
||
)
|
||
|
||
code, new_id_token = pyscape.standard_login(id_token)
|
||
# tup предполагается как кортеж, где второй элемент – обновлённый id_token
|
||
pyscape.utils.write_to_config_file(new_id_token, "id_token")
|
||
|
||
session_id = pyscape.get_game_session(new_id_token)
|
||
accounts = pyscape.get_accounts(session_id)
|
||
logging.info("Found {} accounts".format(len(accounts)))
|
||
|
||
try:
|
||
account = pyscape.ui.get_chosen_account(user, accounts)
|
||
except pyscape.NoAccountChosenError:
|
||
logging.info("No account was chosen")
|
||
return
|
||
|
||
pyscape.game.launch_game(session_id, account)
|
||
|
||
|
||
def handle_social_auth(payload, refresh: bool = False):
|
||
"""
|
||
Обрабатывает social_auth: обменивает код на токен, сохраняет данные и запускает игру.
|
||
"""
|
||
if not refresh:
|
||
try:
|
||
token = pyscape.exchange(payload["code"])
|
||
except Exception as e:
|
||
raise e
|
||
else:
|
||
print("REFRESH")
|
||
token = payload
|
||
|
||
# Если метод extra недоступен, возможно, токен представлен как словарь
|
||
id_token = token.get("id_token")
|
||
if not id_token:
|
||
raise Exception("id_token not found in token response")
|
||
|
||
pyscape.utils.write_to_config_file(id_token, "id_token")
|
||
|
||
# Вычисляем время истечения токена как текущее время + время жизни токена (в секундах)
|
||
expiry = int(time.time() + token["expires_in"] * 1000)
|
||
token["expiry"] = expiry
|
||
|
||
# Сериализуем OAuth токен в JSON
|
||
token_json = json.dumps(token)
|
||
pyscape.utils.write_to_config_file(token_json, "token")
|
||
launch_game_with_token(token, id_token)
|
||
|
||
|
||
def get_cached_tokens():
|
||
"""
|
||
Читает сохранённые токены из конфигурационного файла.
|
||
Если срок действия токена истёк (или почти истёк), возбуждает исключение.
|
||
"""
|
||
token_json = pyscape.utils.read_from_config_file("token")
|
||
try:
|
||
token = json.loads(token_json)
|
||
except json.JSONDecodeError as e:
|
||
raise Exception("Invalid token JSON") from e
|
||
|
||
import datetime
|
||
|
||
current_time = datetime.datetime.now()
|
||
|
||
# Если до истечения токена осталось менее 30 минут, считаем его недействительным.
|
||
if (
|
||
datetime.datetime.fromtimestamp(token["expiry"]) - current_time
|
||
) <= datetime.timedelta(minutes=30):
|
||
raise Exception("cached token has expired")
|
||
|
||
id_token = pyscape.utils.read_from_config_file("id_token")
|
||
return token, id_token
|
||
|
||
|
||
def handle_regular_launch():
|
||
"""
|
||
Пытается использовать сохранённые токены для запуска игры,
|
||
иначе инициирует стандартный процесс аутентификации.
|
||
"""
|
||
try:
|
||
token, id_token = get_cached_tokens()
|
||
try:
|
||
refresh_result = pyscape.refresh_token(token["refresh_token"])
|
||
handle_social_auth(refresh_result, True)
|
||
except Exception as e:
|
||
print(str(e))
|
||
pyscape.login()
|
||
return
|
||
except Exception as e:
|
||
logging.info(
|
||
f"Could not load cached tokens, initiating regular login flow: {e}"
|
||
)
|
||
pyscape.login()
|
||
return
|
||
|
||
logging.info("Loaded cached tokens")
|
||
try:
|
||
launch_game_with_token(token, id_token)
|
||
except Exception as e:
|
||
print(e)
|
||
logging.info(
|
||
"Could not login with cached tokens, initiating regular login flow"
|
||
)
|
||
pyscape.login()
|
||
|
||
|
||
def main():
|
||
"""
|
||
Основная функция приложения.
|
||
Если аргументы командной строки не заданы – используется регулярный запуск,
|
||
иначе выполняется разбор intent из payload.
|
||
"""
|
||
# Если отсутствуют дополнительные аргументы, инициируем регулярный запуск.
|
||
if len(sys.argv) < 2:
|
||
try:
|
||
handle_regular_launch()
|
||
except Exception as e:
|
||
logging.error(e)
|
||
sys.exit(1)
|
||
sys.exit(0)
|
||
|
||
# Если передан аргумент, то он рассматривается как payload для intent.
|
||
payload = pyscape.utils.parse_intent_payload(sys.argv[1])
|
||
if "intent" not in payload:
|
||
logging.fatal("No intent found in payload: {}".format(payload))
|
||
sys.exit(1)
|
||
|
||
if payload["intent"] == "social_auth":
|
||
try:
|
||
handle_social_auth(payload)
|
||
except Exception as e:
|
||
pyscape.utils.die(e)
|
||
raise
|
||
|
||
input("Press Enter to exit...")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|