Add SQLite library with API, library desktop apps and MobyGames scraper

This commit is contained in:
SirBlob committed 2026-11-02 23:26:31 -05:00
1 parent 6674428346
commit a16444451a
69 files changed
+2988 -1026

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
import json
import urllib.error
import urllib.parse
import urllib.request
from envfile import env_value
DEFAULT_API_URL = "http://localhost:5000"
IMAGE_PATH_PREFIX = "/api/library/images/"
REQUEST_TIMEOUT_SECONDS = 60
class LibraryError(ValueError):
pass
def api_url() -> str:
return (env_value("LIBRARY_API_URL") or DEFAULT_API_URL).rstrip("/")
def api_key() -> str:
key = env_value("LIBRARY_API_KEY")
if not key:
raise LibraryError("Set LIBRARY_API_KEY in .env to change the library")
return key
def error_message(error: urllib.error.HTTPError) -> str:
try:
return json.loads(error.read().decode("utf-8")).get("message") or error.reason
except ValueError:
return str(error.reason)
def send(method: str, path: str, body: dict | None = None, signed: bool = True):
headers = {"Accept": "application/json"}
data = None
if signed:
headers["Authorization"] = f"Bearer {api_key()}"
if body is not None:
headers["Content-Type"] = "application/json"
data = json.dumps(body).encode("utf-8")
request = urllib.request.Request(f"{api_url()}{path}", data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT_SECONDS) as response:
text = response.read().decode("utf-8")
except urllib.error.HTTPError as error:
raise LibraryError(f"{api_url()} said {error.code}: {error_message(error)}") from error
except urllib.error.URLError as error:
raise LibraryError(f"Could not reach {api_url()}: {error.reason}") from error
return json.loads(text) if text else None
def quoted(identifier: str) -> str:
return urllib.parse.quote(identifier, safe="")
def fetch_library() -> dict:
return send("GET", "/api/library", signed=False)
def save_media(imdb_id: str, fields: dict) -> dict:
return send("PUT", f"/api/library/media/{quoted(imdb_id)}", fields)
def delete_media(imdb_id: str) -> None:
send("DELETE", f"/api/library/media/{quoted(imdb_id)}")
def save_book(goodreads_id: str, fields: dict) -> dict:
return send("PUT", f"/api/library/books/{quoted(goodreads_id)}", fields)
def delete_book(goodreads_id: str) -> None:
send("DELETE", f"/api/library/books/{quoted(goodreads_id)}")
def is_stored_image(path: str | None) -> bool:
return bool(path) and path.startswith(IMAGE_PATH_PREFIX)