mirror of
https://github.com/wizarrrr/wizarr.git
synced 2025-12-23 23:59:23 -05:00
feat: enhance Komga integration with API key updates and user management improvements closes [BUG] Komga Setup Returning a 401 error when Komga is set up and working fine
Fixes #693
This commit is contained in:
@@ -421,6 +421,8 @@ def image_proxy():
|
||||
headers["X-Emby-Token"] = server.api_key
|
||||
elif server.server_type == "plex":
|
||||
headers["X-Plex-Token"] = server.api_key
|
||||
elif server.server_type == "komga":
|
||||
headers["X-API-Key"] = server.api_key
|
||||
|
||||
# Fetch the image
|
||||
r = requests.get(url, headers=headers, timeout=10, stream=True)
|
||||
|
||||
@@ -30,7 +30,7 @@ class KomgaClient(RestApiMixin):
|
||||
def _headers(self) -> dict[str, str]:
|
||||
headers = {"Accept": "application/json"}
|
||||
if self.token:
|
||||
headers["Authorization"] = f"Bearer {self.token}"
|
||||
headers["X-API-Key"] = self.token
|
||||
return headers
|
||||
|
||||
def libraries(self) -> dict[str, str]:
|
||||
@@ -57,7 +57,7 @@ class KomgaClient(RestApiMixin):
|
||||
"""
|
||||
try:
|
||||
if url and token:
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
headers = {"X-API-Key": token}
|
||||
response = requests.get(
|
||||
f"{url.rstrip('/')}/api/v1/libraries", headers=headers, timeout=10
|
||||
)
|
||||
@@ -72,15 +72,31 @@ class KomgaClient(RestApiMixin):
|
||||
logging.warning("Komga: failed to scan libraries – %s", exc)
|
||||
return {}
|
||||
|
||||
def create_user(self, username: str, password: str, email: str) -> str:
|
||||
"""Create a new Komga user and return the user ID."""
|
||||
payload = {"email": email, "password": password, "roles": ["USER"]}
|
||||
response = self.post("/api/v1/users", json=payload)
|
||||
def create_user(
|
||||
self, username: str, password: str, email: str, allow_downloads: bool = False
|
||||
) -> str:
|
||||
"""Create a new Komga user and return the user ID.
|
||||
|
||||
Args:
|
||||
username: Username for the new user (not used by Komga, only email)
|
||||
password: Password for the new user
|
||||
email: Email address for the new user
|
||||
allow_downloads: Whether to grant FILE_DOWNLOAD role
|
||||
|
||||
Returns:
|
||||
str: The new user's ID
|
||||
"""
|
||||
roles = ["USER"]
|
||||
if allow_downloads:
|
||||
roles.append("FILE_DOWNLOAD")
|
||||
|
||||
payload = {"email": email, "password": password, "roles": roles}
|
||||
response = self.post("/api/v2/users", json=payload)
|
||||
return response.json()["id"]
|
||||
|
||||
def update_user(self, user_id: str, updates: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Update a Komga user."""
|
||||
response = self.patch(f"/api/v1/users/{user_id}", json=updates)
|
||||
response = self.patch(f"/api/v2/users/{user_id}", json=updates)
|
||||
return response.json()
|
||||
|
||||
def disable_user(self, user_id: str) -> bool:
|
||||
@@ -104,7 +120,7 @@ class KomgaClient(RestApiMixin):
|
||||
|
||||
def delete_user(self, user_id: str) -> None:
|
||||
"""Delete a Komga user."""
|
||||
self.delete(f"/api/v1/users/{user_id}")
|
||||
self.delete(f"/api/v2/users/{user_id}")
|
||||
|
||||
def get_user(self, user_id: str) -> dict[str, Any]:
|
||||
"""Get user info in legacy format for backward compatibility."""
|
||||
@@ -132,18 +148,35 @@ class KomgaClient(RestApiMixin):
|
||||
)
|
||||
|
||||
# Get raw user data from Komga API
|
||||
response = self.get(f"/api/v1/users/{user_id}")
|
||||
response = self.get(f"/api/v2/users/{user_id}")
|
||||
raw_user = response.json()
|
||||
|
||||
# Extract permissions using utility
|
||||
roles = raw_user.get("roles", [])
|
||||
permissions = StandardizedPermissions.for_basic_server(
|
||||
"komga",
|
||||
is_admin="ADMIN" in raw_user.get("roles", []),
|
||||
allow_downloads=True, # Comic reader allows downloads
|
||||
is_admin="ADMIN" in roles,
|
||||
allow_downloads="FILE_DOWNLOAD" in roles,
|
||||
)
|
||||
|
||||
# Komga gives full access to all libraries
|
||||
library_access = LibraryAccessHelper.create_full_access()
|
||||
# Handle library access - always return actual library names
|
||||
|
||||
if raw_user.get("sharedAllLibraries", False):
|
||||
# User has access to all libraries - fetch all library IDs from server
|
||||
all_libraries = self.libraries() # Returns {id: name} mapping
|
||||
shared_library_ids = list(all_libraries.keys())
|
||||
else:
|
||||
# User has restricted library access
|
||||
shared_library_ids = raw_user.get("sharedLibrariesIds", [])
|
||||
|
||||
# Always create restricted access with the actual library IDs
|
||||
library_access = (
|
||||
LibraryAccessHelper.create_restricted_access(
|
||||
shared_library_ids, getattr(self, "server_id", None)
|
||||
)
|
||||
if shared_library_ids
|
||||
else []
|
||||
)
|
||||
|
||||
# Parse dates
|
||||
created_at = DateHelper.parse_iso_date(raw_user.get("createdDate"))
|
||||
@@ -164,7 +197,7 @@ class KomgaClient(RestApiMixin):
|
||||
def list_users(self) -> list[User]:
|
||||
"""Sync users from Komga into the local DB and return the list of User records."""
|
||||
try:
|
||||
response = self.get("/api/v1/users")
|
||||
response = self.get("/api/v2/users")
|
||||
komga_users = {u["id"]: u for u in response.json()}
|
||||
|
||||
for komga_user in komga_users.values():
|
||||
@@ -193,23 +226,35 @@ class KomgaClient(RestApiMixin):
|
||||
User.server_id == getattr(self, "server_id", None)
|
||||
).all()
|
||||
|
||||
# Add default policy attributes (Komga doesn't have specific download/live TV policies)
|
||||
# Add policy attributes including library access from Komga
|
||||
for user in users:
|
||||
# Get the full user data from Komga to extract library info
|
||||
komga_user_data = komga_users.get(user.token, {})
|
||||
roles = komga_user_data.get("roles", [])
|
||||
|
||||
# Check for FILE_DOWNLOAD role to determine download permission
|
||||
allow_downloads = "FILE_DOWNLOAD" in roles
|
||||
|
||||
# Store both server-specific and standardized keys in policies dict
|
||||
komga_policies = {
|
||||
# Server-specific data (Komga user info would go here)
|
||||
# Server-specific data (Komga user info)
|
||||
"enabled": True, # Komga users are enabled by default
|
||||
"sharedAllLibraries": komga_user_data.get(
|
||||
"sharedAllLibraries", False
|
||||
),
|
||||
"sharedLibrariesIds": komga_user_data.get("sharedLibrariesIds", []),
|
||||
"roles": roles,
|
||||
# Standardized permission keys for UI display
|
||||
"allow_downloads": True, # Default to True for reading apps
|
||||
"allow_downloads": allow_downloads,
|
||||
"allow_live_tv": False, # Komga doesn't have Live TV
|
||||
"allow_sync": True, # Default to True for reading apps
|
||||
}
|
||||
user.set_raw_policies(komga_policies)
|
||||
|
||||
# Update standardized User model columns
|
||||
user.allow_downloads = True # Default for reading apps
|
||||
user.allow_downloads = allow_downloads
|
||||
user.allow_live_tv = False # Komga doesn't have Live TV
|
||||
user.is_admin = False # Would need API call to determine
|
||||
user.is_admin = "ADMIN" in roles
|
||||
|
||||
# Single commit for all metadata updates
|
||||
try:
|
||||
@@ -219,19 +264,116 @@ class KomgaClient(RestApiMixin):
|
||||
db.session.rollback()
|
||||
return []
|
||||
|
||||
# Cache detailed metadata for all users (including library access)
|
||||
self._cache_user_metadata_from_bulk_response(users, komga_users)
|
||||
|
||||
# Commit the standardized metadata updates
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"Komga: failed to commit standardized metadata updates: {e}"
|
||||
)
|
||||
db.session.rollback()
|
||||
|
||||
return users
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to list Komga users: {e}")
|
||||
return []
|
||||
|
||||
def _cache_user_metadata_from_bulk_response(
|
||||
self, users: list[User], komga_users: dict
|
||||
) -> None:
|
||||
"""Cache user metadata from bulk API response without individual API calls.
|
||||
|
||||
Args:
|
||||
users: List of User objects to cache metadata for
|
||||
komga_users: Dictionary of raw user data from bulk /api/v2/users response
|
||||
"""
|
||||
if not users or not komga_users:
|
||||
return
|
||||
|
||||
from app.services.media.utils import (
|
||||
DateHelper,
|
||||
LibraryAccessHelper,
|
||||
StandardizedPermissions,
|
||||
create_standardized_user_details,
|
||||
)
|
||||
|
||||
cached_count = 0
|
||||
for user in users:
|
||||
try:
|
||||
# Get the raw user data from bulk response
|
||||
raw_user = komga_users.get(user.token)
|
||||
if not raw_user:
|
||||
continue
|
||||
|
||||
roles = raw_user.get("roles", [])
|
||||
|
||||
# Extract standardized permissions
|
||||
permissions = StandardizedPermissions.for_basic_server(
|
||||
"komga",
|
||||
is_admin="ADMIN" in roles,
|
||||
allow_downloads="FILE_DOWNLOAD" in roles,
|
||||
)
|
||||
|
||||
# Handle library access - always return actual library names
|
||||
if raw_user.get("sharedAllLibraries", False):
|
||||
# User has access to all libraries - fetch all library IDs from server
|
||||
all_libraries = self.libraries() # Returns {id: name} mapping
|
||||
shared_library_ids = list(all_libraries.keys())
|
||||
else:
|
||||
# User has restricted library access
|
||||
shared_library_ids = raw_user.get("sharedLibrariesIds", [])
|
||||
|
||||
# Always create restricted access with the actual library IDs
|
||||
library_access = (
|
||||
LibraryAccessHelper.create_restricted_access(
|
||||
shared_library_ids, getattr(self, "server_id", None)
|
||||
)
|
||||
if shared_library_ids
|
||||
else []
|
||||
)
|
||||
|
||||
# Parse dates
|
||||
created_at = DateHelper.parse_iso_date(raw_user.get("createdDate"))
|
||||
last_active = DateHelper.parse_iso_date(raw_user.get("lastActiveDate"))
|
||||
|
||||
# Create standardized user details
|
||||
details = create_standardized_user_details(
|
||||
user_id=user.token,
|
||||
username=raw_user.get("email", "Unknown"),
|
||||
email=raw_user.get("email"),
|
||||
permissions=permissions,
|
||||
library_access=library_access,
|
||||
raw_policies=raw_user,
|
||||
created_at=created_at,
|
||||
last_active=last_active,
|
||||
is_enabled=True, # Komga doesn't have a disabled state
|
||||
)
|
||||
|
||||
# Update the standardized metadata columns in the User record
|
||||
user.update_standardized_metadata(details)
|
||||
cached_count += 1
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"Failed to cache metadata for Komga user {user.token}: {e}"
|
||||
)
|
||||
continue
|
||||
|
||||
if cached_count > 0:
|
||||
logging.info(f"Cached metadata for {cached_count} Komga users")
|
||||
|
||||
def _set_library_access(self, user_id: str, library_ids: list[str]) -> None:
|
||||
"""Set library access for a user."""
|
||||
if not library_ids:
|
||||
return
|
||||
|
||||
try:
|
||||
for library_id in library_ids:
|
||||
self.put(f"/api/v1/users/{user_id}/shared-libraries/{library_id}")
|
||||
# Use v2 API to set library access via user update
|
||||
updates = {"sharedLibraries": {"all": False, "libraryIds": library_ids}}
|
||||
self.patch(f"/api/v2/users/{user_id}", json=updates)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to set library access for user {user_id}: {e}")
|
||||
|
||||
@@ -258,11 +400,27 @@ class KomgaClient(RestApiMixin):
|
||||
return False, "User or e-mail already exists."
|
||||
|
||||
try:
|
||||
user_id = self.create_user(username, password, email)
|
||||
|
||||
inv = Invitation.query.filter_by(code=code).first()
|
||||
|
||||
current_server_id = getattr(self, "server_id", None)
|
||||
|
||||
# Get download permission from invitation, or fall back to server default
|
||||
if inv and inv.allow_downloads is not None:
|
||||
allow_downloads = inv.allow_downloads
|
||||
else:
|
||||
# Use server's default download policy
|
||||
from app.models import MediaServer
|
||||
|
||||
server = (
|
||||
MediaServer.query.get(current_server_id)
|
||||
if current_server_id
|
||||
else None
|
||||
)
|
||||
allow_downloads = server.allow_downloads if server else False
|
||||
|
||||
user_id = self.create_user(
|
||||
username, password, email, allow_downloads=allow_downloads
|
||||
)
|
||||
|
||||
if inv and inv.libraries:
|
||||
library_ids = [
|
||||
lib.external_id
|
||||
@@ -339,7 +497,7 @@ class KomgaClient(RestApiMixin):
|
||||
|
||||
# User statistics - only what's displayed in UI
|
||||
try:
|
||||
users_response = self.get("/api/v1/users").json()
|
||||
users_response = self.get("/api/v2/users").json()
|
||||
stats["user_stats"] = {
|
||||
"total_users": len(users_response),
|
||||
"active_sessions": 0, # Komga doesn't have active sessions concept
|
||||
@@ -389,7 +547,7 @@ class KomgaClient(RestApiMixin):
|
||||
else:
|
||||
# Ultimate fallback: API call
|
||||
try:
|
||||
users = self.get("/api/v1/users").json()
|
||||
users = self.get("/api/v2/users").json()
|
||||
count = len(users) if isinstance(users, list) else 0
|
||||
except Exception as api_error:
|
||||
logging.warning(f"Komga API fallback failed: {api_error}")
|
||||
@@ -449,3 +607,45 @@ class KomgaClient(RestApiMixin):
|
||||
"content_stats": {},
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
def get_recent_items(self, limit: int = 6) -> list[dict[str, str]]:
|
||||
"""Get recently added books from Komga for the wizard widget.
|
||||
|
||||
Args:
|
||||
limit: Maximum number of items to return
|
||||
|
||||
Returns:
|
||||
list: List of dicts with 'title' and 'thumb' keys
|
||||
"""
|
||||
try:
|
||||
# Get latest books from Komga API
|
||||
response = self.get(f"/api/v1/books/latest?size={limit}")
|
||||
books = response.json().get("content", [])
|
||||
|
||||
items = []
|
||||
for book in books:
|
||||
# Get book ID for thumbnail
|
||||
book_id = book.get("id")
|
||||
|
||||
if book_id:
|
||||
# Construct thumbnail URL with authentication
|
||||
thumb_url = (
|
||||
f"{self.url.rstrip('/')}/api/v1/books/{book_id}/thumbnail"
|
||||
)
|
||||
|
||||
# Generate secure proxy URL with opaque token
|
||||
thumb_url = self.generate_image_proxy_url(thumb_url)
|
||||
|
||||
items.append(
|
||||
{
|
||||
"title": book.get("metadata", {}).get("title")
|
||||
or book.get("name", "Unknown"),
|
||||
"thumb": thumb_url,
|
||||
}
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to get recent items from Komga: {e}")
|
||||
return []
|
||||
|
||||
@@ -148,12 +148,12 @@ def check_komga(url: str, token: str) -> tuple[bool, str]:
|
||||
|
||||
We perform a lightweight GET request to ``/api/v1/libraries`` which is
|
||||
available to authenticated users and returns a list of libraries in
|
||||
JSON. When *token* is set we send it as a *Bearer* header.
|
||||
JSON. When *token* is set we send it as an *X-API-Key* header.
|
||||
"""
|
||||
try:
|
||||
headers = {"Accept": "application/json"}
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
headers["X-API-Key"] = token
|
||||
|
||||
resp = requests.get(
|
||||
f"{url.rstrip('/')}/api/v1/libraries", headers=headers, timeout=10
|
||||
|
||||
@@ -112,22 +112,22 @@
|
||||
function closeModal(){ document.getElementById('create-server-modal').innerHTML=''; }
|
||||
function updateOptions(){
|
||||
const type=document.getElementById('server_type').value;
|
||||
// Show universal media server options for Plex, Jellyfin, Emby, and Audiobookshelf
|
||||
const showMediaOptions = ['plex', 'jellyfin', 'emby', 'audiobookshelf'].includes(type);
|
||||
// Show universal media server options for Plex, Jellyfin, Emby, Audiobookshelf, and Komga
|
||||
const showMediaOptions = ['plex', 'jellyfin', 'emby', 'audiobookshelf', 'komga'].includes(type);
|
||||
document.getElementById('media-server-options').style.display = showMediaOptions ? 'block' : 'none';
|
||||
|
||||
// Hide "Allow Live TV" for Audiobookshelf since it doesn't support Live TV
|
||||
|
||||
// Hide "Allow Live TV" for Audiobookshelf and Komga since they don't support Live TV
|
||||
const allowLiveTvOption = document.getElementById('allow-live-tv-option');
|
||||
if (allowLiveTvOption) {
|
||||
allowLiveTvOption.style.display = type === 'audiobookshelf' ? 'none' : 'block';
|
||||
allowLiveTvOption.style.display = ['audiobookshelf', 'komga'].includes(type) ? 'none' : 'block';
|
||||
}
|
||||
|
||||
// Hide "Allow Mobile Uploads" for Audiobookshelf and Jellyfin since they don't support mobile uploads
|
||||
|
||||
// Hide "Allow Mobile Uploads" for Audiobookshelf, Jellyfin, and Komga since they don't support mobile uploads
|
||||
const allowMobileUploadsOption = document.getElementById('allow-mobile-uploads-option');
|
||||
if (allowMobileUploadsOption) {
|
||||
allowMobileUploadsOption.style.display = ['plex', 'emby'].includes(type) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
|
||||
document.getElementById('romm-options').style.display = type==='romm' ? 'block' : 'none';
|
||||
document.getElementById('api-key-div').style.display = type==='romm' ? 'none' : 'block';
|
||||
}
|
||||
|
||||
@@ -114,22 +114,22 @@
|
||||
function closeModal(){ document.getElementById('create-server-modal').innerHTML=''; }
|
||||
function updateOptions(){
|
||||
const type=document.getElementById('server_type').value;
|
||||
// Show universal media server options for Plex, Jellyfin, Emby, and Audiobookshelf
|
||||
const showMediaOptions = ['plex', 'jellyfin', 'emby', 'audiobookshelf'].includes(type);
|
||||
// Show universal media server options for Plex, Jellyfin, Emby, Audiobookshelf, and Komga
|
||||
const showMediaOptions = ['plex', 'jellyfin', 'emby', 'audiobookshelf', 'komga'].includes(type);
|
||||
document.getElementById('media-server-options').style.display = showMediaOptions ? 'block' : 'none';
|
||||
|
||||
// Hide "Allow Live TV" for Audiobookshelf since it doesn't support Live TV
|
||||
|
||||
// Hide "Allow Live TV" for Audiobookshelf and Komga since they don't support Live TV
|
||||
const allowLiveTvOption = document.getElementById('allow-live-tv-option-edit');
|
||||
if (allowLiveTvOption) {
|
||||
allowLiveTvOption.style.display = type === 'audiobookshelf' ? 'none' : 'block';
|
||||
allowLiveTvOption.style.display = ['audiobookshelf', 'komga'].includes(type) ? 'none' : 'block';
|
||||
}
|
||||
|
||||
// Hide "Allow Mobile Uploads" for Audiobookshelf and Jellyfin since they don't support mobile uploads
|
||||
|
||||
// Hide "Allow Mobile Uploads" for Audiobookshelf, Jellyfin, and Komga since they don't support mobile uploads
|
||||
const allowMobileUploadsOption = document.getElementById('allow-mobile-uploads-option-edit');
|
||||
if (allowMobileUploadsOption) {
|
||||
allowMobileUploadsOption.style.display = ['plex', 'emby'].includes(type) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
|
||||
document.getElementById('romm-options').style.display = type==='romm' ? 'block' : 'none';
|
||||
document.getElementById('api-key-div').style.display = type==='romm' ? 'none' : 'block';
|
||||
}
|
||||
|
||||
@@ -237,6 +237,12 @@
|
||||
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary dark:peer-focus:ring-primary rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary dark:peer-checked:bg-primary"></div>
|
||||
<span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">{{ _("Allow Downloads") }}</span>
|
||||
</label>
|
||||
{% elif s.server_type == "komga" %}
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input id="allow_downloads_{{ s.id }}" name="allow_downloads" type="checkbox" value="true" {% if s.allow_downloads %}checked{% endif %} class="sr-only peer">
|
||||
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary dark:peer-focus:ring-primary rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary dark:peer-checked:bg-primary"></div>
|
||||
<span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">{{ _("Allow Downloads") }}</span>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
<!-- Library dropdown-search -->
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
---
|
||||
title: "Welcome to Komga!"
|
||||
title: "{{ _('What is Komga?') }}"
|
||||
---
|
||||
|
||||
# 📚 {{ _("Welcome to Your Digital Library") }}
|
||||
## 📚 {{ _('Welcome to Our Komga Server') }}
|
||||
|
||||
{{ _("You've been invited to access our Komga comic and manga library! "
|
||||
"Komga is a digital library server that organizes and serves your comics, "
|
||||
"manga, and digital books.") }}
|
||||
{{ _('Great news — you now have access to our **digital comic and manga library** through Komga!') }}
|
||||
{{ widget:recently_added_media }}
|
||||
|
||||
## 🎯 {{ _("What can you do?") }}
|
||||
|||
|
||||
### 📖 {{ _('What is Komga?') }}
|
||||
|
||||
- **Browse Collections**: {{ _("Explore organized series and collections") }}
|
||||
- **Read Online**: {{ _("Read directly in your web browser") }}
|
||||
- **Download**: {{ _("Download comics for offline reading") }}
|
||||
- **Track Progress**: {{ _("Keep track of what you've read") }}
|
||||
- **Multiple Formats**: {{ _("Support for CBZ, CBR, PDF, and more") }}
|
||||
{{ _('Komga is a free, open-source media server designed specifically for comics, manga, and digital books. If you\'re here, it means you\'ve been invited to join our library — welcome!') }}
|
||||
|||
|
||||
|
||||
## 🚀 {{ _("Getting Started") }}
|
||||
|
||||
{{ _("You can access Komga through:") }}
|
||||
|
||||
1. **Web Interface**: {{ _("Use any web browser to access your library") }}
|
||||
2. **Mobile Apps**: {{ _("Download compatible comic reader apps") }}
|
||||
3. **Desktop Apps**: {{ _("Use desktop comic readers that support OPDS") }}
|
||||
|
||||
{{ _("Let's get you set up!") }}
|
||||
|||
|
||||
### 🎯 {{ _('What You\'ll Get') }}
|
||||
- {{ _('Access to our constantly updated collection of comics and manga') }}
|
||||
- {{ _('Read online directly in your browser') }}
|
||||
- {{ _('Download for offline reading on your favorite devices') }}
|
||||
- {{ _('Track your reading progress automatically') }}
|
||||
- {{ _('Support for multiple formats (CBZ, CBR, PDF, EPUB)') }}
|
||||
|||
|
||||
|
||||
@@ -1,37 +1,21 @@
|
||||
---
|
||||
title: "How to Access Your Comics"
|
||||
title: "{{ _('Download Komga Clients') }}"
|
||||
---
|
||||
|
||||
# 💻 {{ _("Accessing Your Library") }}
|
||||
## 💾 {{ _('Download Komga Clients') }}
|
||||
|
||||
## 🌐 {{ _("Web Interface") }}
|
||||
{{ _('Komga has many compatible readers from which you can enjoy your comics and manga!') }}
|
||||
|
||||
{{ _("The easiest way to start reading is through your web browser:") }}
|
||||
|||
|
||||
### 📱 {{ _('Read Anywhere') }}
|
||||
|
||||
1. **Visit**: {{ server_url }}
|
||||
2. **Login**: {{ _("Use the credentials you just created") }}
|
||||
3. **Browse**: {{ _("Explore your available comics and series") }}
|
||||
4. **Read**: {{ _("Click any comic to start reading online") }}
|
||||
{{ _('Komga works with many popular comic readers — install apps on your favorite devices:') }}
|
||||
|
||||
[{{ _("Open Komga Web Interface") }}]({{ server_url }}){:target="_blank" .btn}
|
||||
- 📱 **{{ _('Mobile') }}**: {{ _('iOS (Panels, Chunky) & Android (Mihon/Tachiyomi, Komga Client)') }}
|
||||
- 🖥️ **{{ _('Desktop') }}**: {{ _('Windows (CDisplayEx, YACReader), macOS & Linux (YACReader)') }}
|
||||
- 📺 **{{ _('Tablets') }}**: {{ _('iPad & Android tablets with comic reader apps') }}
|
||||
- 🌐 **{{ _('Web App') }}**: {{ _('Read instantly in your browser at') }} {{ server_url }}
|
||||
- 📖 **{{ _('E-Readers') }}**: {{ _('KOReader and other OPDS-compatible readers') }}
|
||||
|||
|
||||
|
||||
## 📱 {{ _("Mobile Apps") }}
|
||||
|
||||
{{ _("For the best mobile reading experience:") }}
|
||||
|
||||
### {{ _("Android") }}
|
||||
- **Mihon (Tachiyomi)**: {{ _("Popular manga reader with Komga support") }}
|
||||
- **Komga Client**: {{ _("Official Komga mobile app") }}
|
||||
- **Moon+ Reader**: {{ _("Support for OPDS feeds") }}
|
||||
|
||||
### {{ _("iOS") }}
|
||||
- **Panels**: {{ _("Premium comic reader with Komga integration") }}
|
||||
- **Chunky Comic Reader**: {{ _("Supports OPDS feeds") }}
|
||||
|
||||
## 🖥️ {{ _("Desktop Apps") }}
|
||||
|
||||
- **YACReader**: {{ _("Cross-platform comic reader") }}
|
||||
- **CDisplayEx**: {{ _("Windows comic reader") }}
|
||||
- **Komga Web**: {{ _("Use the web interface in full-screen mode") }}
|
||||
|
||||
{{ _("Most apps support OPDS feeds - ask your admin for the OPDS URL if needed.") }}
|
||||
{{ widget:button url="https://komga.org/guides/apps.html" text=_("📚 View Compatible Readers") }}
|
||||
|
||||
@@ -1,42 +1,34 @@
|
||||
---
|
||||
title: "Library Features"
|
||||
title: "{{ _('Library Features') }}"
|
||||
---
|
||||
|
||||
# ✨ {{ _("Library Features") }}
|
||||
## ✨ {{ _('Komga Features') }}
|
||||
|
||||
## 📖 {{ _("Reading Experience") }}
|
||||
{{ _('Discover all the features available to enhance your reading experience!') }}
|
||||
|
||||
- **Webtoon Mode**: {{ _("Perfect for manga and webtoons") }}
|
||||
- **Page-by-Page**: {{ _("Traditional comic reading") }}
|
||||
- **Zoom & Pan**: {{ _("Detailed view of artwork") }}
|
||||
- **Bookmarks**: {{ _("Save your progress automatically") }}
|
||||
|||
|
||||
### 📖 {{ _('Reading Experience') }}
|
||||
|
||||
## 🗂️ {{ _("Organization") }}
|
||||
- **{{ _('Webtoon Mode') }}**: {{ _('Perfect for manga and webtoons with continuous scrolling') }}
|
||||
- **{{ _('Page-by-Page') }}**: {{ _('Traditional comic book reading experience') }}
|
||||
- **{{ _('Zoom & Pan') }}**: {{ _('Get detailed views of artwork') }}
|
||||
- **{{ _('Auto Bookmarks') }}**: {{ _('Your progress is saved automatically') }}
|
||||
|||
|
||||
|
||||
- **Series**: {{ _("Comics grouped by series") }}
|
||||
- **Collections**: {{ _("Curated collections by themes") }}
|
||||
- **Tags**: {{ _("Filter by genres, authors, and more") }}
|
||||
- **Search**: {{ _("Find specific titles quickly") }}
|
||||
|||
|
||||
### 🗂️ {{ _('Organization') }}
|
||||
|
||||
## 👤 {{ _("Personal Features") }}
|
||||
- **{{ _('Series Collections') }}**: {{ _('Browse comics organized by series') }}
|
||||
- **{{ _('Custom Collections') }}**: {{ _('Curated collections by themes and genres') }}
|
||||
- **{{ _('Smart Tags') }}**: {{ _('Filter by genres, authors, publishers, and more') }}
|
||||
- **{{ _('Advanced Search') }}**: {{ _('Find specific titles instantly') }}
|
||||
|||
|
||||
|
||||
- **Reading Progress**: {{ _("Track which issues you've read") }}
|
||||
- **Continue Reading**: {{ _("Pick up where you left off") }}
|
||||
- **Reading Lists**: {{ _("Create custom reading lists") }}
|
||||
- **Favorites**: {{ _("Mark your favorite series") }}
|
||||
|||
|
||||
### 👤 {{ _('Personal Features') }}
|
||||
|
||||
## 🔍 {{ _("Discovery") }}
|
||||
|
||||
- **Latest Releases**: {{ _("See newly added comics") }}
|
||||
- **Recently Updated**: {{ _("Series with new issues") }}
|
||||
- **Recommendations**: {{ _("Based on your reading history") }}
|
||||
|
||||
## 💾 {{ _("Offline Reading") }}
|
||||
|
||||
{{ _("Many compatible apps allow you to:") }}
|
||||
|
||||
- **Download**: {{ _("Save comics locally") }}
|
||||
- **Sync**: {{ _("Keep progress synchronized") }}
|
||||
- **Offline Mode**: {{ _("Read without internet") }}
|
||||
|
||||
{{ _("Your reading progress syncs back when you reconnect!") }}
|
||||
- **{{ _('Reading Progress') }}**: {{ _('Track which issues you\'ve completed') }}
|
||||
- **{{ _('Continue Reading') }}**: {{ _('Pick up exactly where you left off') }}
|
||||
- **{{ _('Reading Lists') }}**: {{ _('Create and manage custom reading lists') }}
|
||||
- **{{ _('On Deck') }}**: {{ _('See your next suggested reads') }}
|
||||
|||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
title: "Tips & Tricks"
|
||||
---
|
||||
|
||||
# 💡 {{ _("Tips for the Best Experience") }}
|
||||
|
||||
## 🎯 {{ _("Reading Tips") }}
|
||||
|
||||
- **Reading Direction**: {{ _("Check if manga should be read right-to-left") }}
|
||||
- **Display Settings**: {{ _("Adjust brightness and contrast for comfort") }}
|
||||
- **Full Screen**: {{ _("Use full-screen mode for immersive reading") }}
|
||||
- **Keyboard Shortcuts**: {{ _("Learn shortcuts for faster navigation") }}
|
||||
|
||||
## 🔧 {{ _("Browser Settings") }}
|
||||
|
||||
- **Zoom Level**: {{ _("Adjust browser zoom for optimal text size") }}
|
||||
- **Dark Mode**: {{ _("Enable dark mode for night reading") }}
|
||||
- **Notifications**: {{ _("Allow notifications for new releases") }}
|
||||
|
||||
## 📱 {{ _("Mobile Reading") }}
|
||||
|
||||
- **Landscape Mode**: {{ _("Some comics work better in landscape") }}
|
||||
- **Gesture Controls**: {{ _("Learn swipe gestures for navigation") }}
|
||||
- **Auto-Brightness**: {{ _("Adjust screen brightness automatically") }}
|
||||
|
||||
## 🎨 {{ _("Customization") }}
|
||||
|
||||
- **Theme**: {{ _("Choose light or dark theme") }}
|
||||
- **Reader Settings**: {{ _("Adjust page fit and reading direction") }}
|
||||
- **Layout**: {{ _("Customize library view (grid, list, etc.)") }}
|
||||
|
||||
## 🔍 {{ _("Organization Tips") }}
|
||||
|
||||
- **Use Search**: {{ _("Search supports title, author, and tags") }}
|
||||
- **Filter by Status**: {{ _("Find ongoing vs completed series") }}
|
||||
- **Sort Options**: {{ _("Sort by added date, name, or progress") }}
|
||||
- **Mark as Read**: {{ _("Keep track of your reading progress") }}
|
||||
|
||||
## 🚀 {{ _("Advanced Features") }}
|
||||
|
||||
- **OPDS Feed**: {{ _("Use OPDS URL in compatible apps") }}
|
||||
- **API Access**: {{ _("For developers - full REST API available") }}
|
||||
- **Metadata**: {{ _("Rich metadata for better organization") }}
|
||||
|
||||
{{ _("Happy reading! 📚✨") }}
|
||||
17
wizard_steps/komga/99_tips.md
Normal file
17
wizard_steps/komga/99_tips.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: "{{ _('Tips for the best experience') }}"
|
||||
---
|
||||
|
||||
## 📚 {{ _('Get the best reading experience') }}
|
||||
|
||||
{{ _('Komga offers a great reading experience, but here\'s how to make it even better:') }}
|
||||
|
||||
1. **{{ _('Choose the right reader mode') }}** – {{ _('Use webtoon mode for manga, page-by-page for traditional comics') }}
|
||||
2. **{{ _('Adjust reading direction') }}** – {{ _('Settings → Reader → set right-to-left for manga') }}
|
||||
3. **{{ _('Enable dark mode') }}** – {{ _('Easier on your eyes during night reading sessions') }}
|
||||
4. **{{ _('Use full-screen mode') }}** – {{ _('Press F11 in browser for distraction-free reading') }}
|
||||
5. **{{ _('Download for offline') }}** – {{ _('Download comics to mobile apps for reading without internet') }}
|
||||
|
||||
*{{ _('Happy reading!') }}* 📖
|
||||
|
||||
{{ widget:button url="external_url" text=_("Go to Komga") }}
|
||||
Reference in New Issue
Block a user