diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index c46e6c38..adcda8ff 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -98,6 +98,10 @@
"leech": "Leech",
"seed": "Seed"
},
+ "freshrss": {
+ "subscriptions": "Subscriptions",
+ "unread": "Unread"
+ },
"changedetectionio": {
"totalObserved": "Total Observed",
"diffsDetected": "Diffs Detected"
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 93cdb995..2a5438c9 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -34,9 +34,8 @@ export default async function credentialedProxyHandler(req, res, map) {
"ghostfolio",
"truenas",
"pterodactyl",
- ].includes(widget.type))
- {
- headers.Authorization = `Bearer ${widget.key}`;
+ ].includes(widget.type)) {
+ headers.Authorization = `Bearer ${widget.key}`;
} else if (widget.type === "proxmox") {
headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
} else if (widget.type === "proxmoxbackupserver") {
@@ -54,6 +53,20 @@ export default async function credentialedProxyHandler(req, res, map) {
} else {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
}
+ } else if (widget.type === "freshrss") {
+ const resp = await fetch(`${widget.url}/api/greader.php/accounts/ClientLogin`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded"
+ },
+ body: new URLSearchParams({
+ Email: widget.username,
+ Passwd: widget.password,
+ })
+ })
+ const text = await resp.text()
+ const [, token] = text.split("\n").find(line => line.startsWith("Auth=")).split("=")
+ headers.Authorization = `GoogleLogin auth=${token}`
} else {
headers["X-API-Key"] = `${widget.key}`;
}
@@ -78,10 +91,10 @@ export default async function credentialedProxyHandler(req, res, map) {
if (status >= 400) {
logger.error("HTTP Error %d calling %s", status, url.toString());
}
-
+
if (status === 200) {
if (!validateWidgetData(widget, endpoint, resultData)) {
- return res.status(500).json({error: {message: "Invalid data", url: sanitizeErrorURL(url), data: resultData}});
+ return res.status(500).json({ error: { message: "Invalid data", url: sanitizeErrorURL(url), data: resultData } });
}
if (map) resultData = map(resultData);
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index ec7be376..6ca5722e 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -17,6 +17,7 @@ const components = {
emby: dynamic(() => import("./emby/component")),
fileflows: dynamic(() => import("./fileflows/component")),
flood: dynamic(() => import("./flood/component")),
+ freshrss: dynamic(() => import("./freshrss/component")),
ghostfolio: dynamic(() => import("./ghostfolio/component")),
gluetun: dynamic(() => import("./gluetun/component")),
gotify: dynamic(() => import("./gotify/component")),
diff --git a/src/widgets/freshrss/component.jsx b/src/widgets/freshrss/component.jsx
new file mode 100644
index 00000000..ee4daf18
--- /dev/null
+++ b/src/widgets/freshrss/component.jsx
@@ -0,0 +1,37 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const { widget } = service;
+
+ const { data: subscriptionsData, error: subscriptionsError } = useWidgetAPI(widget, "subscriptions");
+ const { data: unreadData, error: unreadError } = useWidgetAPI(widget, "unread");
+
+ if (subscriptionsError) {
+ return ;
+ }
+ if (unreadError) {
+ return ;
+ }
+
+ if (!subscriptionsData || !unreadData) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/widgets/freshrss/widget.js b/src/widgets/freshrss/widget.js
new file mode 100644
index 00000000..ea87fa38
--- /dev/null
+++ b/src/widgets/freshrss/widget.js
@@ -0,0 +1,24 @@
+import { asJson } from "utils/proxy/api-helpers";
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+ api: "{url}/api/greader.php/{endpoint}?output=json",
+ proxyHandler: credentialedProxyHandler,
+
+ mappings: {
+ subscriptions: {
+ endpoint: "reader/api/0/subscription/list",
+ map: (data) => ({
+ count: asJson(data).subscriptions.length
+ }),
+ },
+ unread: {
+ endpoint: "reader/api/0/unread-count",
+ map: (data) => ({
+ count: asJson(data).max
+ }),
+ }
+ }
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index a8ce5282..dca03173 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -12,6 +12,7 @@ import downloadstation from "./downloadstation/widget";
import emby from "./emby/widget";
import fileflows from "./fileflows/widget";
import flood from "./flood/widget";
+import freshrss from "./freshrss/widget";
import ghostfolio from "./ghostfolio/widget"
import gluetun from "./gluetun/widget";
import gotify from "./gotify/widget";
@@ -90,6 +91,7 @@ const widgets = {
emby,
fileflows,
flood,
+ freshrss,
ghostfolio,
gluetun,
gotify,