Enhancement: support komga API keys, breaking API changes (#4874)

This commit is contained in:
shamoon 2025-03-04 08:16:10 -08:00 committed by GitHub
parent 548b5f8081
commit d26ec27942
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 113 additions and 17 deletions

View File

@ -9,6 +9,11 @@ Uses the same username and password used to login from the web.
Allowed fields: `["libraries", "series", "books"]`.
| Komga API Version | Homepage Widget Version |
| ----------------- | ----------------------- |
| < v2 | 1 (default) |
| >= v2 | 2 |
```yaml
widget:
type: komga

View File

@ -304,7 +304,7 @@ export function cleanServiceGroups(groups) {
// frigate
enableRecentEvents,
// beszel, glances, immich, mealie, pihole, pfsense, speedtest
// beszel, glances, immich, komga, mealie, pihole, pfsense, speedtest
version,
// glances
@ -482,7 +482,7 @@ export function cleanServiceGroups(groups) {
if (snapshotHost) widget.snapshotHost = snapshotHost;
if (snapshotPath) widget.snapshotPath = snapshotPath;
}
if (["beszel", "glances", "immich", "mealie", "pfsense", "pihole", "speedtest"].includes(type)) {
if (["beszel", "glances", "immich", "komga", "mealie", "pfsense", "pihole", "speedtest"].includes(type)) {
if (version) widget.version = parseInt(version, 10);
}
if (type === "glances") {

View File

@ -8,7 +8,7 @@ export function setCookieHeader(url, params) {
const existingCookie = cookieJar.getCookieStringSync(url.toString());
if (existingCookie) {
params.headers = params.headers ?? {};
params.headers.Cookie = existingCookie;
params.headers[params.cookieHeader ?? "Cookie"] = existingCookie;
}
}

View File

@ -8,16 +8,13 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: libraryData, error: libraryError } = useWidgetAPI(widget, "libraries");
const { data: seriesData, error: seriesError } = useWidgetAPI(widget, "series");
const { data: bookData, error: bookError } = useWidgetAPI(widget, "books");
const { data: komgaData, error: komgaError } = useWidgetAPI(widget);
if (libraryError || seriesError || bookError) {
const finalError = libraryError ?? seriesError ?? bookError;
return <Container service={service} error={finalError} />;
if (komgaError) {
return <Container service={service} error={komgaError} />;
}
if (!libraryData || !seriesData || !bookData) {
if (!komgaData) {
return (
<Container service={service}>
<Block label="komga.libraries" />
@ -27,9 +24,11 @@ export default function Component({ service }) {
);
}
const { libraries: libraryData, series: seriesData, books: bookData } = komgaData;
return (
<Container service={service}>
<Block label="komga.libraries" value={t("common.number", { value: libraryData.total })} />
<Block label="komga.libraries" value={t("common.number", { value: libraryData.length })} />
<Block label="komga.series" value={t("common.number", { value: seriesData.totalElements })} />
<Block label="komga.books" value={t("common.number", { value: bookData.totalElements })} />
</Container>

View File

@ -0,0 +1,86 @@
import getServiceWidget from "utils/config/service-helpers";
import { formatApiCall } from "utils/proxy/api-helpers";
import { httpProxy } from "utils/proxy/http";
import widgets from "widgets/widgets";
import createLogger from "utils/logger";
const proxyName = "komgaProxyHandler";
const logger = createLogger(proxyName);
export default async function komgaProxyHandler(req, res) {
const { group, service, index } = req.query;
if (group && service) {
const widget = await getServiceWidget(group, service, index);
if (!widgets?.[widget.type]?.api) {
return res.status(403).json({ error: "Service does not support API calls" });
}
if (widget) {
try {
const data = {};
const headers = {
accept: "application/json",
"Content-Type": "application/json",
};
if (widget.username && widget.password) {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
} else if (widget.key) {
headers["X-API-Key"] = widget.key;
}
const librariesURL = formatApiCall(widgets?.[widget.type].api, { ...widget, endpoint: "libraries" });
const [librariesStatus, , librariesData] = await httpProxy(librariesURL, {
method: "GET",
headers,
cookieHeader: "X-Auth-Token",
});
if (librariesStatus !== 200) {
return res.status(librariesStatus).send(data);
}
data.libraries = JSON.parse(Buffer.from(librariesData).toString()).filter((library) => !library.unavailable);
const seriesEndpointName = widget.version === 2 ? "seriesv2" : "series";
const seriesEndpoint = widgets[widget.type].mappings[seriesEndpointName].endpoint;
const seriesURL = formatApiCall(widgets?.[widget.type].api, { ...widget, endpoint: seriesEndpoint });
const [seriesStatus, , seriesData] = await httpProxy(seriesURL, {
method: widgets[widget.type].mappings[seriesEndpointName].method || "GET",
headers,
body: "{}",
cookieHeader: "X-Auth-Token",
});
if (seriesStatus !== 200) {
return res.status(seriesStatus).send(data);
}
data.series = JSON.parse(Buffer.from(seriesData).toString());
const booksEndpointName = widget.version === 2 ? "booksv2" : "books";
const booksEndpoint = widgets[widget.type].mappings[booksEndpointName].endpoint;
const booksURL = formatApiCall(widgets?.[widget.type].api, { ...widget, endpoint: booksEndpoint });
const [booksStatus, , booksData] = await httpProxy(booksURL, {
method: widgets[widget.type].mappings[booksEndpointName].method || "GET",
headers,
body: "{}",
cookieHeader: "X-Auth-Token",
});
if (booksStatus !== 200) {
return res.status(booksStatus).send(data);
}
data.books = JSON.parse(Buffer.from(booksData).toString());
return res.send(data);
} catch (e) {
logger.error("Error communicating with Komga API: %s", e);
return res.status(500).json({ error: "Error communicating with Komga API" });
}
}
}
return res.status(400).json({ error: "Invalid proxy service type" });
}

View File

@ -1,25 +1,31 @@
import genericProxyHandler from "utils/proxy/handlers/generic";
import { jsonArrayFilter } from "utils/proxy/api-helpers";
import komgaProxyHandler from "./proxy";
const widget = {
api: "{url}/api/v1/{endpoint}",
proxyHandler: genericProxyHandler,
proxyHandler: komgaProxyHandler,
mappings: {
libraries: {
endpoint: "libraries",
map: (data) => ({
total: jsonArrayFilter(data, (item) => !item.unavailable).length,
}),
},
series: {
endpoint: "series",
validate: ["totalElements"],
},
seriesv2: {
endpoint: "series/list",
method: "POST",
validate: ["totalElements"],
},
books: {
endpoint: "books",
validate: ["totalElements"],
},
booksv2: {
endpoint: "books/list",
method: "POST",
validate: ["totalElements"],
},
},
};