resolve review issues
This commit is contained in:
parent
a1f86a4470
commit
04a9be5c93
@ -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
|
type: suwayomi
|
||||||
widget:
|
url: http://suwayomi.host.or.ip
|
||||||
type: suwayomi
|
username: username #optional
|
||||||
url: http://suwayomi.host.or.ip
|
password: password #optional
|
||||||
username: username
|
category: 0 #optional, defaults to all categories
|
||||||
password: password
|
|
||||||
category: 0
|
|
||||||
fields:
|
|
||||||
- download
|
|
||||||
- nondownload
|
|
||||||
- read
|
|
||||||
- unread
|
|
||||||
- downloadedRead
|
|
||||||
- downloadedunread
|
|
||||||
- nondownloadedread
|
|
||||||
- nondownloadedunread
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user