resolve review issues

This commit is contained in:
Robonau 2024-11-12 01:33:45 +00:00 committed by shamoon
parent a1f86a4470
commit 04a9be5c93
3 changed files with 43 additions and 173 deletions

View File

@ -5,28 +5,16 @@ description: Suwayomi Widget Configuration
Learn more about [Suwayomi](https://github.com/Suwayomi/Suwayomi-Server). Learn more about [Suwayomi](https://github.com/Suwayomi/Suwayomi-Server).
all supported fields shown in example yaml, though a max of 4 will show at one time. Allowed fields:["download", "nondownload", "read", "unread", "downloadedread", "downloadedunread", "nondownloadedread", "nondownloadedunread"]
The default fields are download, nondownload, read and unread.
category defaults to "all" if left unset or set to not a number. The widget defaults to the first four above. If more than four fields are provided, only the first 4 are displayed.
The category ID can be obtained from the url when navigating to it, `?tab={categoryID}`. Category IDs can be obtained from the url when navigating to it, `?tab={categoryID}`.
username and password are available if you have basic auth setup for Suwayomi.
```yaml ```yaml
widget: widget:
icon: https://raw.githubusercontent.com/Suwayomi/Suwayomi-Server/refs/heads/master/server/src/main/resources/icon/faviconlogo-128.png
widget:
type: suwayomi type: suwayomi
url: http://suwayomi.host.or.ip url: http://suwayomi.host.or.ip
username: username username: username #optional
password: password password: password #optional
category: 0 category: 0 #optional, defaults to all categories
fields:
- download
- nondownload
- read
- unread
- downloadedRead
- downloadedunread
- nondownloadedread
- nondownloadedunread
``` ```

View File

@ -4,43 +4,11 @@ import Container from "components/services/widget/container";
import Block from "components/services/widget/block"; import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
/**
* @param {string[]|null} Fields
* @returns {string[]}
*/
function makeFields(Fields = []) {
let fields = Fields ?? [];
if (fields.length === 0) {
fields = ["download", "nonDownload", "read", "unRead"];
}
if (fields.length > 4) {
fields.length = 4;
}
fields = fields.map((field) => field.toLowerCase());
return fields;
}
export default function Component({ service }) { export default function Component({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
/**
* @type {{
* widget: {
* fields: string[]|null
* }
* }}
*/
const { widget } = service; const { widget } = service;
/**
* @type {{
* error: unknown
* data: ({
* label: string, count: number
* }[]),
* }}
*/
const { data: suwayomiData, error: suwayomiError } = useWidgetAPI(widget); const { data: suwayomiData, error: suwayomiError } = useWidgetAPI(widget);
if (suwayomiError) { if (suwayomiError) {
@ -48,10 +16,15 @@ export default function Component({ service }) {
} }
if (!suwayomiData) { if (!suwayomiData) {
const fields = makeFields(widget.fields); if (!widget.fields || widget.fields.length === 0) {
widget.fields = ["download", "nondownload", "read", "unread"];
} else if (widget.fields.length > 4) {
widget.fields = widget.fields.slice(0, 4);
widget.fields = widget.fields.map((field) => field.toLowerCase());
}
return ( return (
<Container service={service}> <Container service={service}>
{fields.map((field) => ( {widget.fields.map((field) => (
<Block key={field} label={`suwayomi.${field}`} /> <Block key={field} label={`suwayomi.${field}`} />
))} ))}
</Container> </Container>

View File

@ -7,69 +7,6 @@ import widgets from "widgets/widgets";
const proxyName = "suwayomiProxyHandler"; const proxyName = "suwayomiProxyHandler";
const logger = createLogger(proxyName); const logger = createLogger(proxyName);
/**
* @typedef {object} countsToExtractItem
* @property {(chapter: chapter) => boolean} condition
* @property {string} gqlCondition
*/
/**
* @typedef totalCount
* @type {object}
* @property {string} totalCount - count
*/
/**
* @typedef ResponseJSON
* @type {{
* data: {
* download: totalCount,
* nondownload: totalCount,
* read: totalCount,
* unread: totalCount,
* downloadedRead: totalCount,
* downloadedunread: totalCount,
* nondownloadedread: totalCount,
* nondownloadedunread: totalCount,
* }
* }}
*/
/**
* @typedef chapter
* @type {{
* isRead: boolean,
* isDownloaded: boolean
* }}
*/
/**
* @typedef ResponseJSONcategory
* @type {{
* data: {
* category: {
* mangas: {
* nodes: {
* chapters: {
* nodes: chapter[]
* }
* }[]
* }
* }
* }
* }}
*/
/**
* @typedef {object} widget
* @property {string} username
* @property {string} password
* @property {string[]|null} fields
* @property {string|number|undefined} category
* @property {keyof typeof widgets} type
*/
/** @type {Record<string, countsToExtractItem>} */
const countsToExtract = { const countsToExtract = {
download: { download: {
condition: (c) => c.isDownloaded, condition: (c) => c.isDownloaded,
@ -79,9 +16,18 @@ const countsToExtract = {
condition: (c) => !c.isDownloaded, condition: (c) => !c.isDownloaded,
gqlCondition: "isDownloaded: false", gqlCondition: "isDownloaded: false",
}, },
read: { condition: (c) => c.isRead, gqlCondition: "isRead: true" }, read: {
unread: { condition: (c) => !c.isRead, gqlCondition: "isRead: false" }, condition: (c) => c.isRead,
downloadedread: { condition: (c) => c.isDownloaded && c.isRead, gqlCondition: "isDownloaded: true, isRead: true" }, gqlCondition: "isRead: true",
},
unread: {
condition: (c) => !c.isRead,
gqlCondition: "isRead: false",
},
downloadedread: {
condition: (c) => c.isDownloaded && c.isRead,
gqlCondition: "isDownloaded: true, isRead: true",
},
downloadedunread: { downloadedunread: {
condition: (c) => c.isDownloaded && !c.isRead, condition: (c) => c.isDownloaded && !c.isRead,
gqlCondition: "isDownloaded: true, isRead: false", gqlCondition: "isDownloaded: true, isRead: false",
@ -96,13 +42,6 @@ const countsToExtract = {
}, },
}; };
/**
* Makes a GraphQL query body based on the provided fieldsSet and category.
*
* @param {string[]} fields - Array of field names.
* @param {string|number|undefined} [category="all"] - Category ID or "all" for general counts.
* @returns {string} - The JSON stringified query body.
*/
function makeBody(fields, category = "all") { function makeBody(fields, category = "all") {
if (Number.isNaN(Number(category))) { if (Number.isNaN(Number(category))) {
let query = ""; let query = "";
@ -148,13 +87,6 @@ function makeBody(fields, category = "all") {
}); });
} }
/**
* Extracts the counts from the response JSON object based on the provided fields.
*
* @param {ResponseJSON|ResponseJSONcategory} responseJSON - The response JSON object.
* @param {string[]} fields - Array of field names.
* @returns
*/
function extractCounts(responseJSON, fields) { function extractCounts(responseJSON, fields) {
if (!("category" in responseJSON.data)) { if (!("category" in responseJSON.data)) {
return fields.map((field) => ({ return fields.map((field) => ({
@ -181,38 +113,6 @@ function extractCounts(responseJSON, fields) {
})); }));
} }
/**
* @param {string[]|null} Fields
* @returns {string[]}
*/
function makeFields(Fields = []) {
let fields = Fields ?? [];
if (fields.length === 0) {
fields = ["download", "nonDownload", "read", "unRead"];
}
if (fields.length > 4) {
fields.length = 4;
}
fields = fields.map((f) => f.toLowerCase());
return fields;
}
/**
* @param {widget} widget
* @returns {{ "Content-Type": string, Authorization?: string }}
*/
function makeHeaders(widget) {
const headers = {
"Content-Type": "application/json",
};
if (widget.username && widget.password) {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
}
return headers;
}
export default async function suwayomiProxyHandler(req, res) { export default async function suwayomiProxyHandler(req, res) {
const { group, service, endpoint } = req.query; const { group, service, endpoint } = req.query;
@ -221,7 +121,6 @@ export default async function suwayomiProxyHandler(req, res) {
return res.status(400).json({ error: "Invalid proxy service type" }); return res.status(400).json({ error: "Invalid proxy service type" });
} }
/** @type {widget} */
const widget = await getServiceWidget(group, service); const widget = await getServiceWidget(group, service);
if (!widget) { if (!widget) {
@ -229,13 +128,24 @@ export default async function suwayomiProxyHandler(req, res) {
return res.status(400).json({ error: "Invalid proxy service type" }); return res.status(400).json({ error: "Invalid proxy service type" });
} }
const fields = makeFields(widget.fields); if (!widget.fields || widget.fields.length === 0) {
widget.fields = ["download", "nondownload", "read", "unread"];
} else if (widget.fields.length > 4) {
widget.fields = widget.fields.slice(0, 4);
widget.fields = widget.fields.map((field) => field.toLowerCase());
}
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget })); const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
const body = makeBody(fields, widget.category); const body = makeBody(widget.fields, widget.category);
const headers = makeHeaders(widget); const headers = {
"Content-Type": "application/json",
};
if (widget.username && widget.password) {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
}
const [status, contentType, data] = await httpProxy(url, { const [status, contentType, data] = await httpProxy(url, {
method: "POST", method: "POST",
@ -259,10 +169,9 @@ export default async function suwayomiProxyHandler(req, res) {
return res.status(status).send({ error: { message: "Error getting data. body: %s, data: %s", body, data } }); return res.status(status).send({ error: { message: "Error getting data. body: %s, data: %s", body, data } });
} }
/** @type {ResponseJSON|ResponseJSONcategory} */
const responseJSON = JSON.parse(data); const responseJSON = JSON.parse(data);
const returnData = extractCounts(responseJSON, fields); const returnData = extractCounts(responseJSON, widget.fields);
if (contentType) res.setHeader("Content-Type", contentType); if (contentType) res.setHeader("Content-Type", contentType);
return res.status(status).send(returnData); return res.status(status).send(returnData);