From 66e7637ea6b750077122fbe6b9f714a470bc2c53 Mon Sep 17 00:00:00 2001 From: Ben Phelps Date: Thu, 25 Aug 2022 21:31:57 +0300 Subject: [PATCH] add ruTorrent widget --- package.json | 2 + pnpm-lock.yaml | 134 ++++++++++++++++++ src/components/services/widget.jsx | 2 + .../services/widgets/service/rutorrent.jsx | 62 ++++++++ src/pages/api/widgets/rutorrent.js | 23 +++ 5 files changed, 223 insertions(+) create mode 100644 src/components/services/widgets/service/rutorrent.jsx create mode 100644 src/pages/api/widgets/rutorrent.js diff --git a/package.json b/package.json index 373a6d47a..da430581c 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "memory-cache": "^0.2.0", "next": "12.2.5", "node-os-utils": "^1.3.7", + "raw-body": "^2.5.1", "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "^4.4.0", + "rutorrent-promise": "^2.0.0", "swr": "^1.3.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7c7a2e0f..7b4915e98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,11 @@ specifiers: next: 12.2.5 node-os-utils: ^1.3.7 postcss: ^8.4.16 + raw-body: ^2.5.1 react: 18.2.0 react-dom: 18.2.0 react-icons: ^4.4.0 + rutorrent-promise: ^2.0.0 swr: ^1.3.0 tailwindcss: ^3.1.8 typescript: ^4.7.4 @@ -29,9 +31,11 @@ dependencies: memory-cache: 0.2.0 next: 12.2.5_biqbaboplfbrettd7655fr4n2y node-os-utils: 1.3.7 + raw-body: 2.5.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-icons: 4.4.0_react@18.2.0 + rutorrent-promise: 2.0.0 swr: 1.3.0_react@18.2.0 devDependencies: @@ -454,6 +458,10 @@ packages: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: true + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /autoprefixer/10.4.8_postcss@8.4.16: resolution: {integrity: sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==} engines: {node: ^10 || ^12 || >=14} @@ -542,6 +550,11 @@ packages: dev: false optional: true + /bytes/3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + /call-bind/1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -597,6 +610,13 @@ packages: /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -682,6 +702,16 @@ packages: /defined/1.0.0: resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==} + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + /detective/5.2.1: resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} engines: {node: '>=0.8.0'} @@ -1144,6 +1174,15 @@ packages: resolution: {integrity: sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==} dev: true + /form-data/3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fraction.js/4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -1290,6 +1329,24 @@ packages: dependencies: function-bind: 1.1.1 + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /iconv-lite/0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false @@ -1529,6 +1586,18 @@ packages: braces: 3.0.2 picomatch: 2.3.1 + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /mini-svg-data-uri/1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true @@ -1617,6 +1686,18 @@ packages: - babel-plugin-macros dev: false + /node-fetch/2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-os-utils/1.3.7: resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==} dev: false @@ -1872,6 +1953,16 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + /raw-body/2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + /react-dom/18.2.0_react@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -1977,6 +2068,15 @@ packages: dependencies: queue-microtask: 1.2.3 + /rutorrent-promise/2.0.0: + resolution: {integrity: sha512-ip6FxYM/BFxRgYSWr+2gZ0ao9LsJ1vJYWyFVnTkSmZrp4Cwa3CFpdxMi/5aZsUf1qve2CY9P4GLvrACx+PZ6yQ==} + dependencies: + form-data: 3.0.1 + node-fetch: 2.6.7 + transitivePeerDependencies: + - encoding + dev: false + /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false @@ -2004,6 +2104,10 @@ packages: lru-cache: 6.0.0 dev: true + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + /shebang-command/2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2049,6 +2153,11 @@ packages: nan: 2.16.0 dev: false + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + /string.prototype.matchall/4.0.7: resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==} dependencies: @@ -2196,6 +2305,15 @@ packages: dependencies: is-number: 7.0.0 + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /tsconfig-paths/3.14.1: resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} dependencies: @@ -2254,6 +2372,11 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /unpipe/1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + /update-browserslist-db/1.0.5_browserslist@4.21.3: resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==} hasBin: true @@ -2286,6 +2409,17 @@ packages: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which-boxed-primitive/1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: diff --git a/src/components/services/widget.jsx b/src/components/services/widget.jsx index f16b5cb5c..8c481f5e2 100644 --- a/src/components/services/widget.jsx +++ b/src/components/services/widget.jsx @@ -6,6 +6,7 @@ import Emby from "./widgets/service/emby"; import Nzbget from "./widgets/service/nzbget"; import Docker from "./widgets/service/docker"; import Pihole from "./widgets/service/pihole"; +import Rutorrent from "./widgets/service/rutorrent"; const widgetMappings = { docker: Docker, @@ -16,6 +17,7 @@ const widgetMappings = { emby: Emby, nzbget: Nzbget, pihole: Pihole, + rutorrent: Rutorrent, }; export default function Widget({ service }) { diff --git a/src/components/services/widgets/service/rutorrent.jsx b/src/components/services/widgets/service/rutorrent.jsx new file mode 100644 index 000000000..e5cb12b68 --- /dev/null +++ b/src/components/services/widgets/service/rutorrent.jsx @@ -0,0 +1,62 @@ +import useSWR from "swr"; +import RuTorrent from "rutorrent-promise"; + +import { formatBytes } from "utils/stats-helpers"; + +import Widget from "../widget"; +import Block from "../block"; + +export default function Rutorrent({ service }) { + const config = service.widget; + + function buildApiUrl() { + const { url, username, password } = config; + + const options = { + url: `${url}/plugins/httprpc/action.php`, + }; + + if (username && password) { + options.username = username; + options.password = password; + } + + const params = new URLSearchParams(options); + + return `/api/widgets/rutorrent?${params.toString()}`; + } + + const { data: statusData, error: statusError } = useSWR(buildApiUrl()); + + if (statusError) { + return ; + } + + if (!statusData) { + return ( + + + + + + ); + } + + const upload = statusData.reduce((acc, torrent) => { + return acc + parseInt(torrent["d.get_up_rate"]); + }, 0); + + const download = statusData.reduce((acc, torrent) => { + return acc + parseInt(torrent["d.get_down_rate"]); + }, 0); + + const active = statusData.filter((torrent) => torrent["d.get_state"] === "1"); + + return ( + + + + + + ); +} diff --git a/src/pages/api/widgets/rutorrent.js b/src/pages/api/widgets/rutorrent.js new file mode 100644 index 000000000..1e60edd90 --- /dev/null +++ b/src/pages/api/widgets/rutorrent.js @@ -0,0 +1,23 @@ +import RuTorrent from "rutorrent-promise"; + +// TODO: Remove the 3rd party dependency once I figure out how to +// call this myself with fetch. Just need to destruct the package. + +export default async function handler(req, res) { + const { url, username, password } = req.query; + + const constructedUrl = new URL(url); + + const rutorrent = new RuTorrent({ + host: constructedUrl.hostname, // default: localhost + port: constructedUrl.port, // default: 80 + path: constructedUrl.pathname, // default: /rutorrent + ssl: constructedUrl.protocol === "https:", // default: false + username: username, // default: none + password: password, // default: none + }); + + const data = await rutorrent.get(["d.get_down_rate", "d.get_up_rate", "d.get_state"]); + + res.status(200).send(data); +}