|
import subprocess |
|
import json |
|
import time |
|
import sys |
|
import os |
|
import threading |
|
import requests |
|
from tkinter import messagebox |
|
|
|
|
|
def show_error(message): |
|
messagebox.showerror("XIVLauncher OTP", message) |
|
|
|
|
|
# ================================ |
|
# CONFIG |
|
# ================================ |
|
def get_launcher_path(): |
|
default_path = path = os.path.join(os.environ["LOCALAPPDATA"], "XIVLauncher", "XIVLauncher.exe") |
|
path_envs = os.environ.get("FFXIV_LAUNCHER_PATH", default_path) |
|
if not os.path.exists(path_envs): |
|
show_error(f"XIVLauncher not found at {path}.") |
|
sys.exit(1) |
|
return path |
|
|
|
|
|
LAUNCHER_PATH = get_launcher_path() |
|
|
|
|
|
def params(): |
|
OTP_ENV = os.environ.get("FFXIV_OTP") |
|
if not OTP_ENV: |
|
show_error( |
|
"The FFXIV_OTP environment variable is not set. It must be in the format VAULT_NAME;ITEM_TITLE;[PORT]." |
|
) |
|
sys.exit(0) |
|
|
|
ENVS = OTP_ENV.split(";") |
|
if len(ENVS) < 2: |
|
show_error( |
|
"The FFXIV_OTP environment variable is not in the correct format. It must be in the format VAULT_NAME;ITEM_TITLE;[PORT]." |
|
) |
|
sys.exit(1) |
|
|
|
VAULT_NAME = ENVS[0].strip() |
|
ITEM_TITLE = ENVS[1].strip() |
|
PORT = ENVS[2].strip() if len(ENVS) > 2 else os.environ.get("FFIXV_OTP_PORT", "4646") |
|
|
|
return VAULT_NAME, ITEM_TITLE, PORT |
|
|
|
|
|
VAULT_NAME, ITEM_TITLE, PORT = params() |
|
|
|
|
|
# ================================ |
|
# PROTON PASS |
|
# ================================ |
|
def get_otp(): |
|
try: |
|
result = subprocess.run( |
|
[ |
|
"pass-cli", |
|
"item", |
|
"totp", |
|
"--vault-name", |
|
VAULT_NAME, |
|
"--item-title", |
|
ITEM_TITLE, |
|
"--output", |
|
"json", |
|
], |
|
capture_output=True, |
|
text=True, |
|
creationflags=subprocess.CREATE_NO_WINDOW, # <<< No console window |
|
) |
|
if result.returncode != 0 or not result.stdout.strip(): |
|
raise Exception(result.stderr.strip()) |
|
data = json.loads(result.stdout) |
|
otp = data.get("totp") |
|
if not otp or len(otp) != 6: |
|
raise Exception(f"OTP invalide: {otp}") |
|
return otp |
|
except Exception as e: |
|
show_error(f"Failed to get OTP: {e}") |
|
exit(1) |
|
|
|
|
|
# ================================ |
|
# WAIT FOR LAUNCHER |
|
# ================================ |
|
def wait_for_launcher(timeout=30): |
|
for _ in range(timeout * 2): # 0.5s interval |
|
try: |
|
requests.get(f"http://127.0.0.1:{PORT}", timeout=1) |
|
return True |
|
except requests.RequestException: |
|
time.sleep(0.5) |
|
return False |
|
|
|
|
|
# ================================ |
|
# MAIN |
|
# ================================ |
|
# Start the launcher in the background |
|
subprocess.Popen([LAUNCHER_PATH]) |
|
|
|
# Get OTP |
|
otp_result = [None] |
|
|
|
|
|
def otp_thread(): |
|
otp_result[0] = get_otp() |
|
|
|
|
|
t = threading.Thread(target=otp_thread) |
|
t.start() |
|
|
|
if not wait_for_launcher(): |
|
show_error(f"XIV doesn't respond on configured {PORT} port.") |
|
sys.exit(1) |
|
|
|
t.join() |
|
OTP = otp_result[0] |
|
|
|
# Inject! |
|
try: |
|
requests.get(f"http://127.0.0.1:{PORT}/ffxivlauncher/{OTP}", timeout=3) |
|
except requests.RequestException: |
|
pass |