diff --git a/docs/widgets/services/fireshare.md b/docs/widgets/services/fireshare.md
new file mode 100644
index 00000000..ab26e403
--- /dev/null
+++ b/docs/widgets/services/fireshare.md
@@ -0,0 +1,16 @@
+---
+title: Fireshare
+description: Fireshare Widget Configuration
+---
+
+Learn more about [Fireshare](https://github.com/ShaneIsrael/fireshare).
+
+Allowed fields: `["total", "categories", "views"]`.
+
+```yaml
+widget:
+ type: fireshare
+ url: http://fireshare.host.or.ip
+ username: username
+ password: password
+```
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 15de0ee9..f4ccde6a 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -882,5 +882,10 @@
"enabled": "Enabled",
"disabled": "Disabled",
"total": "Total"
+ },
+ "fireshare": {
+ "total": "Total",
+ "categories": "Categories",
+ "views": "Views"
}
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 1b5c4b68..b89b201f 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -27,6 +27,7 @@ const components = {
esphome: dynamic(() => import("./esphome/component")),
evcc: dynamic(() => import("./evcc/component")),
fileflows: dynamic(() => import("./fileflows/component")),
+ fireshare: dynamic(() => import("./fireshare/component")),
flood: dynamic(() => import("./flood/component")),
freshrss: dynamic(() => import("./freshrss/component")),
fritzbox: dynamic(() => import("./fritzbox/component")),
diff --git a/src/widgets/fireshare/component.jsx b/src/widgets/fireshare/component.jsx
new file mode 100644
index 00000000..353f72d1
--- /dev/null
+++ b/src/widgets/fireshare/component.jsx
@@ -0,0 +1,44 @@
+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 { widget } = service;
+
+ const { data: infoData, error: infoError } = useWidgetAPI(widget);
+
+ if (!widget.fields) {
+ widget.fields = ["total", "categories", "views"];
+ }
+
+ if (infoError) {
+ return ;
+ }
+
+ if (!infoData) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ const total = infoData.videos.length;
+ const categoriesSet = new Set();
+ infoData.videos.forEach(video => {
+ const category = video.path.split('/')[0];
+ categoriesSet.add(category);
+ });
+ const categoriesCount = categoriesSet.size;
+ const totalViews = infoData.videos.reduce((acc, video) => acc + video.view_count, 0);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/fireshare/proxy.js b/src/widgets/fireshare/proxy.js
new file mode 100644
index 00000000..d736228b
--- /dev/null
+++ b/src/widgets/fireshare/proxy.js
@@ -0,0 +1,74 @@
+import cache from "memory-cache";
+
+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 = "fireshareProxyHandler";
+const logger = createLogger(proxyName);
+const sessionTokenCacheKey = `${proxyName}__sessionToken`;
+
+async function login(widget, service) {
+ const url = formatApiCall(widgets[widget.type].api, { ...widget, endpoint: "login" });
+ logger.info(`url: ${url}`);
+ logger.info(`username: ${widget.username}, password: ${widget.password}`);
+ const [, , , responseHeaders] = await httpProxy(url, {
+ method: "POST",
+ body: JSON.stringify({ username: widget.username, password: widget.password }),
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+
+ try {
+ logger.info(responseHeaders);
+ const rememberTokenCookie = responseHeaders["set-cookie"]
+ .find((cookie) => cookie.startsWith("remember_token="))
+ .split(";")[0]
+ .replace("remember_token=", "");
+ logger.info(`remember_token: ${rememberTokenCookie}`);
+ cache.put(`${sessionTokenCacheKey}.${service}`, rememberTokenCookie);
+ return rememberTokenCookie;
+ } catch (e) {
+ logger.error(`Error retrieving 'remember_token' cookie for service: ${service}`);
+ cache.del(`${sessionTokenCacheKey}.${service}`);
+ return null;
+ }
+}
+
+export default async function fireshareProxyHandler(req, res) {
+ const { group, service } = req.query;
+
+ if (group && service) {
+ const widget = await getServiceWidget(group, service);
+
+ if (!widgets?.[widget.type]?.api) {
+ return res.status(403).json({ error: "Service does not support API calls" });
+ }
+
+ if (widget) {
+ let token = cache.get(`${sessionTokenCacheKey}.${service}`);
+ if (!token) {
+ token = await login(widget, service);
+ if (!token) {
+ return res.status(500).json({ error: "Failed to authenticate with Fireshare" });
+ }
+ }
+ const [, , data] = await httpProxy(
+ formatApiCall(widgets[widget.type].api, { ...widget, endpoint: "videos?sort=updated_at+desc" }),
+ {
+ headers: {
+ "Content-Type": "application/json",
+ Cookie: `remember_token=${token}`
+ }
+ }
+ );
+
+ return res.json(JSON.parse(data));
+ }
+ }
+
+ return res.status(400).json({ error: "Invalid proxy service type" });
+}
diff --git a/src/widgets/fireshare/widget.js b/src/widgets/fireshare/widget.js
new file mode 100644
index 00000000..b27e2816
--- /dev/null
+++ b/src/widgets/fireshare/widget.js
@@ -0,0 +1,8 @@
+import fireshareProxyHandler from "./proxy";
+
+const widget = {
+ api: "{url}/api/{endpoint}",
+ proxyHandler: fireshareProxyHandler,
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index d6965f50..76b5f6df 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -21,6 +21,7 @@ import emby from "./emby/widget";
import esphome from "./esphome/widget";
import evcc from "./evcc/widget";
import fileflows from "./fileflows/widget";
+import fireshare from "./fireshare/widget";
import flood from "./flood/widget";
import freshrss from "./freshrss/widget";
import fritzbox from "./fritzbox/widget";
@@ -136,6 +137,7 @@ const widgets = {
esphome,
evcc,
fileflows,
+ fireshare,
flood,
freshrss,
fritzbox,