Jfa refresh fix
This commit is contained in:
2
.env
2
.env
@@ -18,6 +18,8 @@ JELLYSEERR_API_KEY=your_api_key_here
|
|||||||
# JFA-Go
|
# JFA-Go
|
||||||
ENABLE_JFA=false
|
ENABLE_JFA=false
|
||||||
JFA_URL=http://localhost:8056
|
JFA_URL=http://localhost:8056
|
||||||
|
JFA_USERNAME=yourusername
|
||||||
|
JFA_PASSWORD=yourpassword
|
||||||
JFA_API_KEY=your_api_key_here
|
JFA_API_KEY=your_api_key_here
|
||||||
|
|
||||||
# QBittorrent
|
# QBittorrent
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
# 1.0.7
|
||||||
|
|
||||||
|
- Fixed JFA-GO API keys expiring. The bot now schedules a key refresh
|
||||||
|
|
||||||
# 1.0.6
|
# 1.0.6
|
||||||
|
|
||||||
- Added Progress bar to Active Streams
|
- Added Progress bar to Active Streams
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ Fill out values in the .env and you're good to go!
|
|||||||
- `!createinvite` - Create a new JFA invite link
|
- `!createinvite` - Create a new JFA invite link
|
||||||
- `!listinvites` - List all active JFA invite links
|
- `!listinvites` - List all active JFA invite links
|
||||||
- `!deleteinvite <code>` - Delete a specific JFA Invite
|
- `!deleteinvite <code>` - Delete a specific JFA Invite
|
||||||
|
- `!refreshjfakey` - Refreshes the JFA API Key Forcefully
|
||||||
|
|
||||||
***⚙️ Admin Bot Commands***
|
***⚙️ Admin Bot Commands***
|
||||||
- `!setprefix` - Change the bots command prefix
|
- `!setprefix` - Change the bots command prefix
|
||||||
|
|||||||
140
app.py
140
app.py
@@ -40,6 +40,8 @@ JELLYSEERR_API_KEY = os.getenv("JELLYSEERR_API_KEY", "")
|
|||||||
|
|
||||||
ENABLE_JFA = os.getenv("ENABLE_JFA", "False").lower() == "true"
|
ENABLE_JFA = os.getenv("ENABLE_JFA", "False").lower() == "true"
|
||||||
JFA_URL = os.getenv("JFA_URL")
|
JFA_URL = os.getenv("JFA_URL")
|
||||||
|
JFA_USERNAME = os.getenv("JFA_USERNAME")
|
||||||
|
JFA_PASSWORD = os.getenv("JFA_PASSWORD")
|
||||||
JFA_API_KEY = os.getenv("JFA_API_KEY")
|
JFA_API_KEY = os.getenv("JFA_API_KEY")
|
||||||
|
|
||||||
ENABLE_QBITTORRENT = os.getenv("ENABLE_QBITTORRENT", "False").lower() == "true"
|
ENABLE_QBITTORRENT = os.getenv("ENABLE_QBITTORRENT", "False").lower() == "true"
|
||||||
@@ -356,6 +358,73 @@ def progress_bar(progress: float, length: int = 20) -> str:
|
|||||||
bar = '█' * filled_length + '░' * (length - filled_length)
|
bar = '█' * filled_length + '░' * (length - filled_length)
|
||||||
return f"[{bar}] {progress*100:.2f}%"
|
return f"[{bar}] {progress*100:.2f}%"
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# JFA-GO HELPERS
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
def refresh_jfa_token() -> bool:
|
||||||
|
"""
|
||||||
|
Authenticate to JFA-Go with username/password (Basic auth) against /token/login,
|
||||||
|
write the returned token to .env (JFA_TOKEN and JFA_API_KEY), and reload env.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
global JFA_TOKEN, JFA_API_KEY
|
||||||
|
|
||||||
|
if not (JFA_URL and JFA_USERNAME and JFA_PASSWORD):
|
||||||
|
print("[JFA] Missing JFA_URL/JFA_USERNAME/JFA_PASSWORD in environment.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
url = JFA_URL.rstrip("/") + "/token/login"
|
||||||
|
headers = {"accept": "application/json"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Option A: let requests build the Basic header
|
||||||
|
r = requests.get(url, auth=(JFA_USERNAME, JFA_PASSWORD), headers=headers, timeout=10)
|
||||||
|
|
||||||
|
# If you prefer to build the header manually (exactly like your curl), use:
|
||||||
|
# creds = f"{JFA_USERNAME}:{JFA_PASSWORD}".encode()
|
||||||
|
# b64 = base64.b64encode(creds).decode()
|
||||||
|
# headers["Authorization"] = f"Basic {b64}"
|
||||||
|
# r = requests.get(url, headers=headers, timeout=10)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
print(f"[JFA] token login failed: {r.status_code} - {r.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
data = r.json() if r.text else {}
|
||||||
|
# try common token fields
|
||||||
|
token = (
|
||||||
|
data.get("token")
|
||||||
|
or data.get("access_token")
|
||||||
|
or data.get("jwt")
|
||||||
|
or data.get("api_key")
|
||||||
|
or data.get("data") # sometimes nested
|
||||||
|
)
|
||||||
|
|
||||||
|
# If API returns {"token": "<token>"} -> good. If it returns a wrapped structure,
|
||||||
|
# try to handle a couple of other shapes:
|
||||||
|
if not token:
|
||||||
|
# if response is {'success': True} or {'invites':...} then no token present
|
||||||
|
# print for debugging
|
||||||
|
print("[JFA] token not found in response JSON:", data)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Persist token to .env under both names (compatibility)
|
||||||
|
_update_env_key("JFA_TOKEN", token)
|
||||||
|
_update_env_key("JFA_API_KEY", token)
|
||||||
|
|
||||||
|
# Update in-memory values and reload env
|
||||||
|
JFA_TOKEN = token
|
||||||
|
JFA_API_KEY = token
|
||||||
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
print("[JFA] Successfully refreshed token and updated .env")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[JFA] Exception while refreshing token: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# DISCORD HELPERS
|
# DISCORD HELPERS
|
||||||
# =====================
|
# =====================
|
||||||
@@ -437,6 +506,26 @@ def create_trial_jellyfin_user(username, password):
|
|||||||
print(f"[Jellyfin] Trial user creation failed. Status: {response.status_code}, Response: {response.text}")
|
print(f"[Jellyfin] Trial user creation failed. Status: {response.status_code}, Response: {response.text}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _update_env_key(key: str, value: str, env_path: str = ".env"):
|
||||||
|
"""Update or append key=value in .env (keeps file order)."""
|
||||||
|
lines = []
|
||||||
|
found = False
|
||||||
|
try:
|
||||||
|
with open(env_path, "r", encoding="utf-8") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
except FileNotFoundError:
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
with open(env_path, "w", encoding="utf-8") as f:
|
||||||
|
for line in lines:
|
||||||
|
if line.strip().startswith(f"{key}="):
|
||||||
|
f.write(f"{key}={value}\n")
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
f.write(line)
|
||||||
|
if not found:
|
||||||
|
f.write(f"{key}={value}\n")
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# EVENTS
|
# EVENTS
|
||||||
@@ -760,6 +849,27 @@ async def clearinvites(ctx):
|
|||||||
print(f"[clearinvites] Error: {e}", exc_info=True)
|
print(f"[clearinvites] Error: {e}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.command()
|
||||||
|
async def refreshjfakey(ctx):
|
||||||
|
"""Admin-only: Force refresh the JFA-Go API key using username/password auth."""
|
||||||
|
if not has_admin_role(ctx.author):
|
||||||
|
await ctx.send("❌ You don’t have permission to use this command.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not ENABLE_JFA:
|
||||||
|
await ctx.send("⚠️ JFA-Go integration is disabled in the configuration.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await ctx.send("🔁 Attempting to refresh JFA token...")
|
||||||
|
success = refresh_jfa_token()
|
||||||
|
if success:
|
||||||
|
await ctx.send("✅ Successfully refreshed the JFA-Go API token and updated `.env`")
|
||||||
|
log_event(f"Admin {ctx.author} forced a JFA API Token refresh")
|
||||||
|
else:
|
||||||
|
await ctx.send("❌ Failed to refresh the JFA-Go API token. Check bot logs for details.")
|
||||||
|
log_event(f"Admin {ctx.author} attempted JFA API Token refresh but failed")
|
||||||
|
|
||||||
|
|
||||||
@bot.command()
|
@bot.command()
|
||||||
async def trialaccount(ctx, username: str = None, password: str = None):
|
async def trialaccount(ctx, username: str = None, password: str = None):
|
||||||
"""Create a 24-hour trial Jellyfin account. DM-only, one-time per user."""
|
"""Create a 24-hour trial Jellyfin account. DM-only, one-time per user."""
|
||||||
@@ -1418,7 +1528,8 @@ async def help_command(ctx):
|
|||||||
jfa_cmds = [
|
jfa_cmds = [
|
||||||
f"`{PREFIX}createinvite` - Create a new JFA invite link",
|
f"`{PREFIX}createinvite` - Create a new JFA invite link",
|
||||||
f"`{PREFIX}listinvites` - List all active JFA invite links",
|
f"`{PREFIX}listinvites` - List all active JFA invite links",
|
||||||
f"`{PREFIX}deleteinvite <code>` - Delete a specific JFA invite"
|
f"`{PREFIX}deleteinvite <code>` - Delete a specific JFA invite",
|
||||||
|
f"`{PREFIX}refreshjfakey` - Refreshes the JFA API Key Forcefully"
|
||||||
]
|
]
|
||||||
embed.add_field(name="🔑 JFA Commands", value="\n".join(jfa_cmds), inline=False)
|
embed.add_field(name="🔑 JFA Commands", value="\n".join(jfa_cmds), inline=False)
|
||||||
|
|
||||||
@@ -1570,6 +1681,30 @@ async def cleanup_task():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Cleanup] Failed to send removed message to sync channel: {e}")
|
print(f"[Cleanup] Failed to send removed message to sync channel: {e}")
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# JFA-Go Scheduled Token Refresh
|
||||||
|
# =====================
|
||||||
|
if ENABLE_JFA:
|
||||||
|
|
||||||
|
@tasks.loop(hours=18)
|
||||||
|
async def refresh_jfa_loop():
|
||||||
|
success = refresh_jfa_token()
|
||||||
|
if success:
|
||||||
|
log_event("[JFA] Successfully refreshed token (scheduled loop).")
|
||||||
|
else:
|
||||||
|
log_event("[JFA] Failed to refresh token (scheduled loop).")
|
||||||
|
|
||||||
|
@refresh_jfa_loop.before_loop
|
||||||
|
async def before_refresh_jfa_loop():
|
||||||
|
await bot.wait_until_ready()
|
||||||
|
log_event("[JFA] Token refresh loop waiting until bot is ready.")
|
||||||
|
|
||||||
|
# Start the loop inside on_ready to ensure event loop exists
|
||||||
|
@bot.event
|
||||||
|
async def on_ready():
|
||||||
|
if not refresh_jfa_loop.is_running():
|
||||||
|
refresh_jfa_loop.start()
|
||||||
|
log_event(f"Bot is ready. Logged in as {bot.user}")
|
||||||
|
|
||||||
@tasks.loop(hours=1)
|
@tasks.loop(hours=1)
|
||||||
async def check_for_updates():
|
async def check_for_updates():
|
||||||
@@ -1615,6 +1750,9 @@ async def on_ready():
|
|||||||
if not cleanup_task.is_running():
|
if not cleanup_task.is_running():
|
||||||
cleanup_task.start()
|
cleanup_task.start()
|
||||||
|
|
||||||
|
if not refresh_jfa_loop.is_running():
|
||||||
|
refresh_jfa_loop.start()
|
||||||
|
|
||||||
if not check_for_updates.is_running():
|
if not check_for_updates.is_running():
|
||||||
check_for_updates.start()
|
check_for_updates.start()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user