rm test.conf
This commit is contained in:
@@ -127,7 +127,9 @@ class MemeUtil:
|
||||
db_conn.row_factory = sqlite3.Row
|
||||
rows_per_page: int = 10
|
||||
offset: int = (page - 1) * rows_per_page
|
||||
query: str = "SELECT id, timestamp FROM memes ORDER BY timestamp DESC LIMIT 10 OFFSET ?"
|
||||
query: str = (
|
||||
"SELECT id, timestamp FROM memes ORDER BY timestamp DESC LIMIT 10 OFFSET ?"
|
||||
)
|
||||
async with await db_conn.execute(query, (offset,)) as db_cursor:
|
||||
results = await db_cursor.fetchall()
|
||||
for result in results:
|
||||
|
||||
@@ -15,11 +15,11 @@ import time
|
||||
|
||||
# Monkey-patch streamrip's Tidal client credentials BEFORE importing TidalClient
|
||||
import streamrip.client.tidal as _tidal_module # type: ignore # noqa: E402
|
||||
|
||||
_tidal_module.CLIENT_ID = "fX2JxdmntZWK0ixT"
|
||||
_tidal_module.CLIENT_SECRET = "1Nn9AfDAjxrgJFJbKNWLeAyKGVGmINuXPPLHVXAvxAg="
|
||||
_tidal_module.AUTH = aiohttp.BasicAuth(
|
||||
login=_tidal_module.CLIENT_ID,
|
||||
password=_tidal_module.CLIENT_SECRET
|
||||
login=_tidal_module.CLIENT_ID, password=_tidal_module.CLIENT_SECRET
|
||||
)
|
||||
|
||||
from streamrip.client import TidalClient # type: ignore # noqa: E402
|
||||
@@ -99,21 +99,21 @@ class SRUtil:
|
||||
|
||||
async def start_keepalive(self) -> None:
|
||||
"""Start the background keepalive task.
|
||||
|
||||
|
||||
This should be called once at startup to ensure the Tidal session
|
||||
stays alive even during idle periods.
|
||||
"""
|
||||
if self._keepalive_task and not self._keepalive_task.done():
|
||||
logging.info("Tidal keepalive task already running")
|
||||
return
|
||||
|
||||
|
||||
# Ensure initial login
|
||||
try:
|
||||
await self._login_and_persist()
|
||||
logging.info("Initial Tidal login successful")
|
||||
except Exception as e:
|
||||
logging.warning("Initial Tidal login failed: %s", e)
|
||||
|
||||
|
||||
self._keepalive_task = asyncio.create_task(self._keepalive_runner())
|
||||
logging.info("Tidal keepalive task started")
|
||||
|
||||
@@ -132,14 +132,14 @@ class SRUtil:
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(self.KEEPALIVE_INTERVAL)
|
||||
|
||||
|
||||
# Check if we've had recent activity
|
||||
if self._last_successful_request:
|
||||
time_since_last = time.time() - self._last_successful_request
|
||||
if time_since_last < self.KEEPALIVE_INTERVAL:
|
||||
# Recent activity, no need to ping
|
||||
continue
|
||||
|
||||
|
||||
# Check if token is expiring soon and proactively refresh
|
||||
if self._is_token_expiring_soon():
|
||||
logging.info("Tidal keepalive: Token expiring soon, refreshing...")
|
||||
@@ -149,7 +149,7 @@ class SRUtil:
|
||||
except Exception as e:
|
||||
logging.warning("Tidal keepalive: Token refresh failed: %s", e)
|
||||
continue
|
||||
|
||||
|
||||
# Check if session is stale
|
||||
if self._is_session_stale():
|
||||
logging.info("Tidal keepalive: Session stale, refreshing...")
|
||||
@@ -157,9 +157,11 @@ class SRUtil:
|
||||
await self._login_and_persist(force=True)
|
||||
logging.info("Tidal keepalive: Session refresh successful")
|
||||
except Exception as e:
|
||||
logging.warning("Tidal keepalive: Session refresh failed: %s", e)
|
||||
logging.warning(
|
||||
"Tidal keepalive: Session refresh failed: %s", e
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
# Make a lightweight API call to keep the session alive
|
||||
if self.streamrip_client.logged_in:
|
||||
try:
|
||||
@@ -178,7 +180,7 @@ class SRUtil:
|
||||
await self._login_and_persist(force=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logging.info("Tidal keepalive task cancelled")
|
||||
break
|
||||
@@ -195,7 +197,9 @@ class SRUtil:
|
||||
tidal.access_token = cached.get("access_token", "")
|
||||
tidal.refresh_token = cached.get("refresh_token", "")
|
||||
tidal.token_expiry = cached.get("token_expiry", "")
|
||||
tidal.country_code = cached.get("country_code", os.getenv("tidal_country_code", ""))
|
||||
tidal.country_code = cached.get(
|
||||
"country_code", os.getenv("tidal_country_code", "")
|
||||
)
|
||||
else:
|
||||
tidal.user_id = os.getenv("tidal_user_id", "")
|
||||
tidal.access_token = os.getenv("tidal_access_token", "")
|
||||
@@ -212,7 +216,9 @@ class SRUtil:
|
||||
with open(TIDAL_TOKEN_CACHE_PATH, "r") as f:
|
||||
data = json.load(f)
|
||||
# Validate required fields exist
|
||||
if all(k in data for k in ("access_token", "refresh_token", "token_expiry")):
|
||||
if all(
|
||||
k in data for k in ("access_token", "refresh_token", "token_expiry")
|
||||
):
|
||||
logging.info("Loaded Tidal tokens from cache")
|
||||
return data
|
||||
except Exception as e:
|
||||
@@ -248,22 +254,25 @@ class SRUtil:
|
||||
|
||||
async def start_device_auth(self) -> tuple[str, str]:
|
||||
"""Start device authorization flow.
|
||||
|
||||
|
||||
Returns:
|
||||
tuple: (device_code, verification_url) - User should visit the URL to authorize.
|
||||
"""
|
||||
if not hasattr(self.streamrip_client, 'session') or not self.streamrip_client.session:
|
||||
if (
|
||||
not hasattr(self.streamrip_client, "session")
|
||||
or not self.streamrip_client.session
|
||||
):
|
||||
self.streamrip_client.session = await self.streamrip_client.get_session()
|
||||
|
||||
|
||||
device_code, verification_url = await self.streamrip_client._get_device_code()
|
||||
return device_code, verification_url
|
||||
|
||||
async def check_device_auth(self, device_code: str) -> tuple[bool, Optional[str]]:
|
||||
"""Check if user has completed device authorization.
|
||||
|
||||
|
||||
Args:
|
||||
device_code: The device code from start_device_auth()
|
||||
|
||||
|
||||
Returns:
|
||||
tuple: (success, error_message)
|
||||
- (True, None) if auth completed successfully
|
||||
@@ -271,7 +280,7 @@ class SRUtil:
|
||||
- (False, error_message) if auth failed
|
||||
"""
|
||||
status, auth_info = await self.streamrip_client._get_auth_status(device_code)
|
||||
|
||||
|
||||
if status == 0:
|
||||
# Success - apply new tokens
|
||||
self._apply_new_tokens(auth_info)
|
||||
@@ -300,7 +309,8 @@ class SRUtil:
|
||||
# token_expiry is typically an ISO timestamp string
|
||||
if isinstance(token_expiry, str):
|
||||
from datetime import datetime
|
||||
expiry_dt = datetime.fromisoformat(token_expiry.replace('Z', '+00:00'))
|
||||
|
||||
expiry_dt = datetime.fromisoformat(token_expiry.replace("Z", "+00:00"))
|
||||
expiry_ts = expiry_dt.timestamp()
|
||||
else:
|
||||
expiry_ts = float(token_expiry)
|
||||
@@ -318,14 +328,14 @@ class SRUtil:
|
||||
|
||||
async def _force_fresh_login(self) -> bool:
|
||||
"""Force a complete fresh login, ignoring logged_in state.
|
||||
|
||||
|
||||
Returns True if login succeeded, False otherwise.
|
||||
"""
|
||||
# Reset the logged_in flag to force a fresh login
|
||||
self.streamrip_client.logged_in = False
|
||||
|
||||
|
||||
# Close existing session if present
|
||||
if hasattr(self.streamrip_client, 'session') and self.streamrip_client.session:
|
||||
if hasattr(self.streamrip_client, "session") and self.streamrip_client.session:
|
||||
try:
|
||||
if not self.streamrip_client.session.closed:
|
||||
await self.streamrip_client.session.close()
|
||||
@@ -333,10 +343,10 @@ class SRUtil:
|
||||
logging.warning("Error closing old session: %s", e)
|
||||
# Use object.__setattr__ to bypass type checking for session reset
|
||||
try:
|
||||
object.__setattr__(self.streamrip_client, 'session', None)
|
||||
object.__setattr__(self.streamrip_client, "session", None)
|
||||
except Exception:
|
||||
pass # Session will be recreated on next login
|
||||
|
||||
|
||||
try:
|
||||
logging.info("Forcing fresh Tidal login...")
|
||||
await self.streamrip_client.login()
|
||||
@@ -345,49 +355,53 @@ class SRUtil:
|
||||
logging.info("Fresh Tidal login successful")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.warning("Forced Tidal login failed: %s - device re-auth may be required", e)
|
||||
logging.warning(
|
||||
"Forced Tidal login failed: %s - device re-auth may be required", e
|
||||
)
|
||||
return False
|
||||
|
||||
async def _login_and_persist(self, force: bool = False) -> None:
|
||||
"""Login to Tidal and persist any refreshed tokens.
|
||||
|
||||
|
||||
Args:
|
||||
force: If True, force a fresh login even if already logged in.
|
||||
|
||||
|
||||
This method now checks for:
|
||||
1. Token expiry - refreshes if token is about to expire
|
||||
2. Session age - refreshes if session is too old
|
||||
3. logged_in state - logs in if not logged in
|
||||
|
||||
|
||||
If refresh fails, logs a warning but does not raise.
|
||||
"""
|
||||
needs_login = force or not self.streamrip_client.logged_in
|
||||
|
||||
|
||||
# Check if token is expiring soon
|
||||
if not needs_login and self._is_token_expiring_soon():
|
||||
logging.info("Tidal token expiring soon, will refresh")
|
||||
needs_login = True
|
||||
|
||||
|
||||
# Check if session is too old
|
||||
if not needs_login and self._is_session_stale():
|
||||
logging.info("Tidal session is stale, will refresh")
|
||||
needs_login = True
|
||||
|
||||
|
||||
if not needs_login:
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
# Reset logged_in to ensure fresh login attempt
|
||||
if force or self._is_token_expiring_soon():
|
||||
self.streamrip_client.logged_in = False
|
||||
|
||||
|
||||
await self.streamrip_client.login()
|
||||
self._last_login_time = time.time()
|
||||
# After login, tokens may have been refreshed - persist them
|
||||
self._save_cached_tokens()
|
||||
logging.info("Tidal login/refresh successful")
|
||||
except Exception as e:
|
||||
logging.warning("Tidal login/refresh failed: %s - device re-auth may be required", e)
|
||||
logging.warning(
|
||||
"Tidal login/refresh failed: %s - device re-auth may be required", e
|
||||
)
|
||||
# Don't mark as logged in on failure - let subsequent calls retry
|
||||
|
||||
async def rate_limited_request(self, func, *args, **kwargs):
|
||||
@@ -397,13 +411,15 @@ class SRUtil:
|
||||
elapsed = now - self.LAST_METADATA_REQUEST
|
||||
if elapsed < self.METADATA_RATE_LIMIT:
|
||||
await asyncio.sleep(self.METADATA_RATE_LIMIT - elapsed)
|
||||
|
||||
|
||||
# Ensure we're logged in before making the request
|
||||
try:
|
||||
await self._login_and_persist()
|
||||
except Exception as e:
|
||||
logging.warning("Pre-request login failed in rate_limited_request: %s", e)
|
||||
|
||||
logging.warning(
|
||||
"Pre-request login failed in rate_limited_request: %s", e
|
||||
)
|
||||
|
||||
result = await func(*args, **kwargs)
|
||||
self.LAST_METADATA_REQUEST = time.time()
|
||||
return result
|
||||
@@ -432,8 +448,11 @@ class SRUtil:
|
||||
try:
|
||||
await self._login_and_persist()
|
||||
except Exception as login_err:
|
||||
logging.warning("Pre-request login failed: %s (continuing anyway)", login_err)
|
||||
|
||||
logging.warning(
|
||||
"Pre-request login failed: %s (continuing anyway)",
|
||||
login_err,
|
||||
)
|
||||
|
||||
result = await func(*args, **kwargs)
|
||||
# Track successful request
|
||||
self._last_successful_request = time.time()
|
||||
@@ -441,7 +460,12 @@ class SRUtil:
|
||||
except AttributeError as e:
|
||||
# Probably missing/closed client internals: try re-login once
|
||||
last_exc = e
|
||||
logging.warning("AttributeError in API call (attempt %d/%d): %s", attempt + 1, retries, e)
|
||||
logging.warning(
|
||||
"AttributeError in API call (attempt %d/%d): %s",
|
||||
attempt + 1,
|
||||
retries,
|
||||
e,
|
||||
)
|
||||
try:
|
||||
await self._force_fresh_login()
|
||||
except Exception:
|
||||
@@ -475,7 +499,10 @@ class SRUtil:
|
||||
|
||||
# Treat 401 (Unauthorized) as an auth failure: force a fresh re-login then retry
|
||||
is_401_error = (
|
||||
(isinstance(e, aiohttp.ClientResponseError) and getattr(e, "status", None) == 401)
|
||||
(
|
||||
isinstance(e, aiohttp.ClientResponseError)
|
||||
and getattr(e, "status", None) == 401
|
||||
)
|
||||
or "401" in msg
|
||||
or "unauthorized" in msg.lower()
|
||||
)
|
||||
@@ -491,7 +518,9 @@ class SRUtil:
|
||||
if login_success:
|
||||
logging.info("Forced re-login after 401 successful")
|
||||
else:
|
||||
logging.warning("Forced re-login after 401 failed - may need device re-auth")
|
||||
logging.warning(
|
||||
"Forced re-login after 401 failed - may need device re-auth"
|
||||
)
|
||||
except Exception as login_exc:
|
||||
logging.warning("Forced login after 401 failed: %s", login_exc)
|
||||
if attempt < retries - 1:
|
||||
@@ -550,9 +579,7 @@ class SRUtil:
|
||||
title_match = self.is_fuzzy_match(expected_title, found_title, threshold)
|
||||
return artist_match and album_match and title_match
|
||||
|
||||
def dedupe_by_key(
|
||||
self, key: str | list[str], entries: list[dict]
|
||||
) -> list[dict]:
|
||||
def dedupe_by_key(self, key: str | list[str], entries: list[dict]) -> list[dict]:
|
||||
"""Return entries de-duplicated by one or more keys."""
|
||||
|
||||
keys = [key] if isinstance(key, str) else list(key)
|
||||
@@ -679,9 +706,11 @@ class SRUtil:
|
||||
"upc": album_json.get("upc"),
|
||||
"album_copyright": album_json.get("copyright"),
|
||||
"album_cover_id": album_json.get("cover"),
|
||||
"album_cover_url": f"https://resources.tidal.com/images/{album_json.get('cover')}/1280x1280.jpg"
|
||||
if album_json.get("cover")
|
||||
else None,
|
||||
"album_cover_url": (
|
||||
f"https://resources.tidal.com/images/{album_json.get('cover')}/1280x1280.jpg"
|
||||
if album_json.get("cover")
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
# Track-level (overrides or adds to album info)
|
||||
@@ -813,7 +842,9 @@ class SRUtil:
|
||||
return None
|
||||
if not metadata:
|
||||
return None
|
||||
albums = self.dedupe_by_key(["title", "releaseDate"], metadata.get("albums", []))
|
||||
albums = self.dedupe_by_key(
|
||||
["title", "releaseDate"], metadata.get("albums", [])
|
||||
)
|
||||
albums_out = [
|
||||
{
|
||||
"artist": ", ".join(artist["name"] for artist in album["artists"]),
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# -----------------------
|
||||
# /m/m2/ PHP handler
|
||||
location ~ ^/m/m2/(.+\.php)$ {
|
||||
alias /storage/music2/completed/;
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME /storage/music2/completed/$1;
|
||||
fastcgi_param DOCUMENT_ROOT /storage/music2/completed;
|
||||
fastcgi_param SCRIPT_NAME /m/m2/$1;
|
||||
}
|
||||
|
||||
# /m/m2/ static files
|
||||
location /m/m2/ {
|
||||
alias /storage/music2/completed/;
|
||||
index index.php;
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
# -----------------------
|
||||
# /m/ PHP handler
|
||||
location ~ ^/m/(.+\.php)$ {
|
||||
root /var/www/codey.lol/new/public;
|
||||
include fastcgi_params;
|
||||
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/$1;
|
||||
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||
fastcgi_param SCRIPT_NAME /m/$1;
|
||||
}
|
||||
|
||||
# /m/ static files
|
||||
location /m/ {
|
||||
root /var/www/codey.lol/new/public;
|
||||
index index.php;
|
||||
try_files $uri $uri/ /m/index.php$is_args$args;
|
||||
}
|
||||
@@ -7,19 +7,18 @@ import os
|
||||
|
||||
VIDEO_PROXY_SECRET = os.environ.get("VIDEO_PROXY_SECRET", "").encode()
|
||||
|
||||
def sign_video_id(video_id: Optional[str|bool]) -> str:
|
||||
|
||||
def sign_video_id(video_id: Optional[str | bool]) -> str:
|
||||
"""Generate a signed token for a video ID."""
|
||||
if not VIDEO_PROXY_SECRET or not video_id:
|
||||
return "" # Return empty if no secret configured
|
||||
|
||||
|
||||
timestamp = int(time.time() * 1000) # milliseconds to match JS Date.now()
|
||||
payload = f"{video_id}:{timestamp}"
|
||||
signature = hmac.new(
|
||||
VIDEO_PROXY_SECRET,
|
||||
payload.encode(),
|
||||
hashlib.sha256
|
||||
VIDEO_PROXY_SECRET, payload.encode(), hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
|
||||
token_data = f"{payload}:{signature}"
|
||||
# base64url encode (no padding, to match JS base64url)
|
||||
return base64.urlsafe_b64encode(token_data.encode()).decode().rstrip("=")
|
||||
return base64.urlsafe_b64encode(token_data.encode()).decode().rstrip("=")
|
||||
|
||||
Reference in New Issue
Block a user