diff --git a/package.json b/package.json index 94be675e0..bbc97e64f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@ctrl/deluge": "^4.0.0", "@ctrl/qbittorrent": "^4.0.0", "@ctrl/shared-torrent": "^4.1.0", + "@ctrl/transmission": "^4.1.1", "@dnd-kit/core": "^6.0.1", "@dnd-kit/sortable": "^7.0.0", "@dnd-kit/utilities": "^3.2.0", diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index ac3b01c83..bdc71c3e7 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -294,20 +294,21 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & /> )} - {form.values.type === 'Deluge' && ( - <> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={form.errors.password && 'Invalid password'} - /> - - )} + {form.values.type === 'Deluge' || + (form.values.type === 'Transmission' && ( + <> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + ))} diff --git a/src/components/modules/downloads/DownloadsModule.tsx b/src/components/modules/downloads/DownloadsModule.tsx index a77ec4976..d804ebd38 100644 --- a/src/components/modules/downloads/DownloadsModule.tsx +++ b/src/components/modules/downloads/DownloadsModule.tsx @@ -23,36 +23,27 @@ export const DownloadsModule: IModule = { export default function DownloadComponent() { const { config } = useConfig(); - const qBittorrentService = config.services - .filter((service) => service.type === 'qBittorrent') - .at(0); - const delugeService = config.services.filter((service) => service.type === 'Deluge').at(0); + const downloadServices = + config.services.filter( + (service) => + service.type === 'qBittorrent' || + service.type === 'Transmission' || + service.type === 'Deluge' + ) ?? []; const hideComplete: boolean = (config?.modules?.[DownloadsModule.title]?.options?.hidecomplete?.value as boolean) ?? false; - - const [delugeTorrents, setDelugeTorrents] = useState([]); - const [qBittorrentTorrents, setqBittorrentTorrents] = useState([]); + const [torrents, setTorrents] = useState([]); const setSafeInterval = useSetSafeInterval(); useEffect(() => { - if (qBittorrentService) { - setSafeInterval(() => { - axios - .post('/api/modules/downloads?dlclient=qbit', { ...qBittorrentService }) - .then((res) => { - setqBittorrentTorrents(res.data.torrents); - }); - }, 3000); - } - if (delugeService) { - setSafeInterval(() => { - axios.post('/api/modules/downloads?dlclient=deluge', { ...delugeService }).then((res) => { - setDelugeTorrents(res.data.torrents); - }); - }, 3000); - } + setSafeInterval(() => { + // Send one request with each download service inside + axios.post('/api/modules/downloads', { config }).then((response) => { + setTorrents(response.data); + }); + }, 1000); }, [config.modules]); - if (!qBittorrentService && !delugeService) { + if (downloadServices.length === 0) { return ( No supported download clients found! @@ -64,7 +55,7 @@ export default function DownloadComponent() { ); } - if (qBittorrentTorrents.length === 0 && delugeTorrents.length === 0) { + if (torrents.length === 0) { return ( <> @@ -85,11 +76,6 @@ export default function DownloadComponent() { ); // Loop over qBittorrent torrents merging with deluge torrents - const torrents: NormalizedTorrent[] = []; - delugeTorrents.forEach((delugeTorrent) => - torrents.push({ ...delugeTorrent, progress: delugeTorrent.progress / 100 }) - ); - qBittorrentTorrents.forEach((torrent) => torrents.push(torrent)); const rows = torrents.map((torrent) => { if (torrent.progress === 1 && hideComplete) { return []; diff --git a/src/components/modules/downloads/TotalDownloadsModule.tsx b/src/components/modules/downloads/TotalDownloadsModule.tsx index 6065066e7..56d73516f 100644 --- a/src/components/modules/downloads/TotalDownloadsModule.tsx +++ b/src/components/modules/downloads/TotalDownloadsModule.tsx @@ -57,42 +57,28 @@ interface torrentHistory { } export default function TotalDownloadsComponent() { + const setSafeInterval = useSetSafeInterval(); const { config } = useConfig(); - const qBittorrentService = config.services - .filter((service) => service.type === 'qBittorrent') - .at(0); - const delugeService = config.services.filter((service) => service.type === 'Deluge').at(0); + const downloadServices = + config.services.filter( + (service) => + service.type === 'qBittorrent' || + service.type === 'Transmission' || + service.type === 'Deluge' + ) ?? []; - const [delugeTorrents, setDelugeTorrents] = useState([]); const [torrentHistory, torrentHistoryHandlers] = useListState([]); - const [qBittorrentTorrents, setqBittorrentTorrents] = useState([]); - - const torrents: NormalizedTorrent[] = []; - delugeTorrents.forEach((delugeTorrent) => - torrents.push({ ...delugeTorrent, progress: delugeTorrent.progress / 100 }) - ); - qBittorrentTorrents.forEach((torrent) => torrents.push(torrent)); + const [torrents, setTorrents] = useState([]); const totalDownloadSpeed = torrents.reduce((acc, torrent) => acc + torrent.downloadSpeed, 0); const totalUploadSpeed = torrents.reduce((acc, torrent) => acc + torrent.uploadSpeed, 0); - const setSafeInterval = useSetSafeInterval(); useEffect(() => { setSafeInterval(() => { - // Get the current download speed of qBittorrent. - if (qBittorrentService) { - axios - .post('/api/modules/downloads?dlclient=qbit', { ...qBittorrentService }) - .then((res) => { - setqBittorrentTorrents(res.data.torrents); - }); - if (delugeService) { - axios.post('/api/modules/downloads?dlclient=deluge', { ...delugeService }).then((res) => { - setDelugeTorrents(res.data.torrents); - }); - } - } + axios.post('/api/modules/downloads', { config }).then((response) => { + setTorrents(response.data); + }); }, 1000); - }, [config.modules]); + }, []); useEffect(() => { torrentHistoryHandlers.append({ @@ -102,7 +88,7 @@ export default function TotalDownloadsComponent() { }); }, [totalDownloadSpeed, totalUploadSpeed]); - if (!qBittorrentService && !delugeService) { + if (downloadServices.length === 0) { return ( No supported download clients found! diff --git a/src/pages/api/modules/downloads.ts b/src/pages/api/modules/downloads.ts index eaa0ece4e..80d636475 100644 --- a/src/pages/api/modules/downloads.ts +++ b/src/pages/api/modules/downloads.ts @@ -1,42 +1,62 @@ import { Deluge } from '@ctrl/deluge'; import { QBittorrent } from '@ctrl/qbittorrent'; +import { NormalizedTorrent } from '@ctrl/shared-torrent'; +import { Transmission } from '@ctrl/transmission'; import { NextApiRequest, NextApiResponse } from 'next'; +import { Config } from '../../../tools/types'; async function Post(req: NextApiRequest, res: NextApiResponse) { // Get the type of service from the request url - const { dlclient } = req.query; - const { body } = req; - // Get login, password and url from the body - const { username, password, url } = body; - if (!dlclient || (!username && !password) || !url) { - return res.status(400).json({ - error: 'Wrong request', + const torrents: NormalizedTorrent[] = []; + const { config }: { config: Config } = req.body; + const qBittorrentService = config.services + .filter((service) => service.type === 'qBittorrent') + .at(0); + const delugeService = config.services.filter((service) => service.type === 'Deluge').at(0); + const transmissionService = config.services + .filter((service) => service.type === 'Transmission') + .at(0); + if (!qBittorrentService && !delugeService && !transmissionService) { + return res.status(500).json({ + statusCode: 500, + message: 'Missing service', }); } - let client: Deluge | QBittorrent; - switch (dlclient) { - case 'qbit': - client = new QBittorrent({ - baseUrl: new URL(url).href, - username, - password, - }); - break; - case 'deluge': - client = new Deluge({ - baseUrl: new URL(url).href, - password, - }); - break; - default: - return res.status(400).json({ - error: 'Wrong request', - }); + if (qBittorrentService) { + torrents.push( + ...( + await new QBittorrent({ + baseUrl: qBittorrentService.url, + username: qBittorrentService.username, + password: qBittorrentService.password, + }).getAllData() + ).torrents + ); } - const data = await client.getAllData(); - res.status(200).json({ - torrents: data.torrents, - }); + if (delugeService) { + const delugeTorrents = ( + await new Deluge({ + baseUrl: delugeService.url, + username: delugeService.username, + password: delugeService.password, + }).getAllData() + ).torrents; + delugeTorrents.forEach((delugeTorrent) => + torrents.push({ ...delugeTorrent, progress: delugeTorrent.progress / 100 }) + ); + } + if (transmissionService) { + torrents.push( + ...( + await new Transmission({ + baseUrl: transmissionService.url, + username: transmissionService.username, + password: transmissionService.password, + }).getAllData() + ).torrents + ); + } + res.status(200).json(torrents); } export default async (req: NextApiRequest, res: NextApiResponse) => { diff --git a/src/tools/types.ts b/src/tools/types.ts index 13c2311db..eeada1521 100644 --- a/src/tools/types.ts +++ b/src/tools/types.ts @@ -31,6 +31,7 @@ export const ServiceTypeList = [ 'Readarr', 'Sonarr', 'qBittorrent', + 'Transmission', ]; export type ServiceType = | 'Other' @@ -41,7 +42,8 @@ export type ServiceType = | 'Radarr' | 'Readarr' | 'Sonarr' - | 'qBittorrent'; + | 'qBittorrent' + | 'Transmission'; export interface serviceItem { id: string; diff --git a/yarn.lock b/yarn.lock index c2d5107a9..55e7f46af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1630,6 +1630,15 @@ __metadata: languageName: node linkType: hard +"@ctrl/shared-torrent@npm:^4.1.1": + version: 4.1.1 + resolution: "@ctrl/shared-torrent@npm:4.1.1" + dependencies: + got: ^12.1.0 + checksum: 1273c9088a920eed5afca945b11e83a6b64d4268ad0b09e916e7e2214ea8092b998ab16525885f8f24af2c75893e3fd7d4542e7e9d6dfe4688da57e47c31b165 + languageName: node + linkType: hard + "@ctrl/torrent-file@npm:^2.0.1": version: 2.0.1 resolution: "@ctrl/torrent-file@npm:2.0.1" @@ -1639,6 +1648,18 @@ __metadata: languageName: node linkType: hard +"@ctrl/transmission@npm:^4.1.1": + version: 4.1.1 + resolution: "@ctrl/transmission@npm:4.1.1" + dependencies: + "@ctrl/magnet-link": ^3.1.0 + "@ctrl/shared-torrent": ^4.1.1 + "@ctrl/url-join": ^2.0.0 + got: ^12.1.0 + checksum: 218ed4c00f70c46c90cd2a5e90f8390beee06a2cf7d76c2445ad2bcfb89ad1e6ea9cf237a7b3aa990fdf81fc9b9d4aa9900fa21e041457e8bb177dbd0b319b0a + languageName: node + linkType: hard + "@ctrl/ts-base32@npm:^2.1.1": version: 2.1.1 resolution: "@ctrl/ts-base32@npm:2.1.1" @@ -9081,7 +9102,7 @@ __metadata: languageName: node linkType: hard -"got@npm:^12.0.1, got@npm:^12.0.4": +"got@npm:^12.0.1, got@npm:^12.0.4, got@npm:^12.1.0": version: 12.1.0 resolution: "got@npm:12.1.0" dependencies: @@ -9398,6 +9419,7 @@ __metadata: "@ctrl/deluge": ^4.0.0 "@ctrl/qbittorrent": ^4.0.0 "@ctrl/shared-torrent": ^4.1.0 + "@ctrl/transmission": ^4.1.1 "@dnd-kit/core": ^6.0.1 "@dnd-kit/sortable": ^7.0.0 "@dnd-kit/utilities": ^3.2.0