From 885b2624a8764fea0136ae35098089e42eb3f373 Mon Sep 17 00:00:00 2001
From: Dawud <7688823+technowhizz@users.noreply.github.com>
Date: Sat, 23 Mar 2024 08:34:07 +0000
Subject: [PATCH] Enhancement: support Jackett widget with admin password
 (#3097) (#3165)

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
---
 docs/widgets/services/jackett.md |  4 +-
 src/utils/proxy/http.js          |  2 +-
 src/widgets/jackett/proxy.js     | 68 ++++++++++++++++++++++++++++++++
 src/widgets/jackett/widget.js    |  5 ++-
 4 files changed, 74 insertions(+), 5 deletions(-)
 create mode 100644 src/widgets/jackett/proxy.js

diff --git a/docs/widgets/services/jackett.md b/docs/widgets/services/jackett.md
index 22e089a4..e102743b 100644
--- a/docs/widgets/services/jackett.md
+++ b/docs/widgets/services/jackett.md
@@ -5,7 +5,7 @@ description: Jackett Widget Configuration
 
 Learn more about [Jackett](https://github.com/Jackett/Jackett).
 
-Jackett must not have any authentication for the widget to work.
+If Jackett has an admin password set, you must set the `password` field for the widget to work.
 
 Allowed fields: `["configured", "errored"]`.
 
@@ -13,5 +13,5 @@ Allowed fields: `["configured", "errored"]`.
 widget:
   type: jackett
   url: http://jackett.host.or.ip
-  key: jackettapikey
+  password: jackettadminpassword # optional
 ```
diff --git a/src/utils/proxy/http.js b/src/utils/proxy/http.js
index ff34ce0d..8a9ce380 100644
--- a/src/utils/proxy/http.js
+++ b/src/utils/proxy/http.js
@@ -103,7 +103,7 @@ export async function httpProxy(url, params = {}) {
 
   try {
     const [status, contentType, data, responseHeaders] = await request;
-    return [status, contentType, data, responseHeaders];
+    return [status, contentType, data, responseHeaders, params];
   } catch (err) {
     logger.error(
       "Error calling %s//%s%s%s...",
diff --git a/src/widgets/jackett/proxy.js b/src/widgets/jackett/proxy.js
new file mode 100644
index 00000000..5292695f
--- /dev/null
+++ b/src/widgets/jackett/proxy.js
@@ -0,0 +1,68 @@
+import { httpProxy } from "utils/proxy/http";
+import { formatApiCall } from "utils/proxy/api-helpers";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+import widgets from "widgets/widgets";
+
+const logger = createLogger("jackettProxyHandler");
+
+async function fetchJackettCookie(widget, loginURL) {
+  const url = new URL(formatApiCall(loginURL, widget));
+  const loginData = `password=${encodeURIComponent(widget.password)}`;
+  const [status, , , , params] = await httpProxy(url, {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/x-www-form-urlencoded",
+    },
+    body: loginData,
+  });
+
+  if (!(status === 200) || !params?.headers?.Cookie) {
+    logger.error("Failed to fetch Jackett cookie, status: %d", status);
+    return null;
+  }
+  return params.headers.Cookie;
+}
+
+export default async function jackettProxyHandler(req, res) {
+  const { group, service, endpoint } = req.query;
+
+  if (!group || !service) {
+    logger.error("Invalid or missing service '%s' or group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const widget = await getServiceWidget(group, service);
+  if (!widget || !widgets[widget.type].api) {
+    logger.error("Invalid or missing widget for service '%s' in group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid widget configuration" });
+  }
+
+  if (widget.password) {
+    const jackettCookie = await fetchJackettCookie(widget, widgets[widget.type].loginURL);
+    if (!jackettCookie) {
+      return res.status(500).json({ error: "Failed to authenticate with Jackett" });
+    }
+    // Add the cookie to the widget for use in subsequent requests
+    widget.headers = { ...widget.headers, Cookie: jackettCookie };
+  }
+
+  const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
+
+  try {
+    const [status, , data] = await httpProxy(url, {
+      method: "GET",
+      headers: widget.headers,
+    });
+
+    if (status !== 200) {
+      logger.error("Error calling Jackett API: %d. Data: %s", status, data);
+      return res.status(status).json({ error: "Failed to call Jackett API", data });
+    }
+
+    return res.status(status).send(data);
+  } catch (error) {
+    logger.error("Exception calling Jackett API: %s", error.message);
+    return res.status(500).json({ error: "Server error", message: error.message });
+  }
+}
diff --git a/src/widgets/jackett/widget.js b/src/widgets/jackett/widget.js
index 9d2a9b5c..0af816e5 100644
--- a/src/widgets/jackett/widget.js
+++ b/src/widgets/jackett/widget.js
@@ -1,8 +1,9 @@
-import genericProxyHandler from "utils/proxy/handlers/generic";
+import jackettProxyHandler from "./proxy";
 
 const widget = {
   api: "{url}/api/v2.0/{endpoint}?apikey={key}&configured=true",
-  proxyHandler: genericProxyHandler,
+  proxyHandler: jackettProxyHandler,
+  loginURL: "{url}/UI/Dashboard",
 
   mappings: {
     indexers: {