From e2286ed99851988d5fae5fcdd106b04ea985fe04 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 8 Oct 2023 22:33:44 +0200 Subject: [PATCH] Add custom scripts for rss download --- .../modals/feeds-modal/DownloadRuleForm.tsx | 7 ++++++ .../modals/feeds-modal/DownloadRulesTab.tsx | 3 +++ client/src/javascript/i18n/strings/en.json | 1 + server/routes/api/feed-monitor.test.ts | 1 + server/services/feedService.ts | 1 + server/util/feedUtil.ts | 23 ++++++++++++++++--- shared/types/Feed.ts | 2 ++ 7 files changed, 35 insertions(+), 3 deletions(-) diff --git a/client/src/javascript/components/modals/feeds-modal/DownloadRuleForm.tsx b/client/src/javascript/components/modals/feeds-modal/DownloadRuleForm.tsx index c5508ac11..c54253cc9 100644 --- a/client/src/javascript/components/modals/feeds-modal/DownloadRuleForm.tsx +++ b/client/src/javascript/components/modals/feeds-modal/DownloadRuleForm.tsx @@ -101,6 +101,13 @@ const DownloadRuleForm: FC = ({ )} + + + { field: formData.field, match: formData.match ?? initialRule.match, exclude: formData.exclude ?? initialRule.exclude, + script: formData.script ?? initialRule.script, destination: formData.destination ?? initialRule.destination, tags: formData.tags?.split(',') ?? initialRule.tags, startOnLoad: formData.startOnLoad ?? initialRule.startOnLoad, diff --git a/client/src/javascript/i18n/strings/en.json b/client/src/javascript/i18n/strings/en.json index 9b7c4220c..46f5d1d91 100644 --- a/client/src/javascript/i18n/strings/en.json +++ b/client/src/javascript/i18n/strings/en.json @@ -102,6 +102,7 @@ "feeds.no.items.matching": "No items matching search term.", "feeds.no.rules.defined": "No rules defined.", "feeds.regEx": "RegEx", + "feeds.script": "Script", "feeds.search": "Search term", "feeds.search.term": "Search term", "feeds.select.feed": "Select Feed", diff --git a/server/routes/api/feed-monitor.test.ts b/server/routes/api/feed-monitor.test.ts index fc06f4431..f0d22ca78 100644 --- a/server/routes/api/feed-monitor.test.ts +++ b/server/routes/api/feed-monitor.test.ts @@ -220,6 +220,7 @@ describe('PUT /api/feed-monitor/rules', () => { feedIDs: [''], match: '', exclude: '.*', + script: '', destination: tempDirectory, tags: ['FeedItem'], startOnLoad: false, diff --git a/server/services/feedService.ts b/server/services/feedService.ts index a2b144e51..db7d4e280 100644 --- a/server/services/feedService.ts +++ b/server/services/feedService.ts @@ -70,6 +70,7 @@ class FeedService extends BaseService> { field: rule.field, match: rule.match, exclude: rule.exclude, + script: rule.script, startOnLoad: rule.startOnLoad, isBasePath: rule.isBasePath, }); diff --git a/server/util/feedUtil.ts b/server/util/feedUtil.ts index c11b36bb7..14feadf6d 100644 --- a/server/util/feedUtil.ts +++ b/server/util/feedUtil.ts @@ -4,6 +4,7 @@ import {cdata as matchCDATA} from '../../shared/util/regEx'; import type {AddTorrentByURLOptions} from '../../shared/schema/api/torrents'; import type {Rule} from '../../shared/types/Feed'; +import { spawn } from 'child_process'; interface PendingDownloadItems extends Required> { @@ -54,17 +55,33 @@ export const getTorrentUrlsFromFeedItem = (feedItem: FeedItem): Array => return []; }; +const execAsync = (...command: string[]) => { + const p = spawn(command[0], command.slice(1)); + return new Promise((resolveFunc) => { + p.stdout.on("data", (x) => { + process.stdout.write(x.toString()); + }); + p.stderr.on("data", (x) => { + process.stderr.write(x.toString()); + }); + p.on("exit", (code) => { + resolveFunc(code); + }); + }); +} + export const getFeedItemsMatchingRules = ( feedItems: Array, rules: Array, ): Array => { return feedItems.reduce((matchedItems: Array, feedItem) => { - rules.forEach((rule) => { + rules.forEach(async (rule) => { const matchField = rule.field ? (feedItem[rule.field] as string) : (feedItem.title as string); - const isMatched = new RegExp(rule.match, 'gi').test(matchField); + const isMatched = rule.match === '' || new RegExp(rule.match, 'gi').test(matchField); const isExcluded = rule.exclude !== '' && new RegExp(rule.exclude, 'gi').test(matchField); + const scriptMatch = rule.script === '' || await execAsync(rule.script) === 80; - if (isMatched && !isExcluded) { + if (isMatched && !isExcluded && scriptMatch) { const torrentUrls = getTorrentUrlsFromFeedItem(feedItem); const isAlreadyDownloaded = matchedItems.some((matchedItem) => torrentUrls.every((url) => matchedItem.urls.includes(url)), diff --git a/shared/types/Feed.ts b/shared/types/Feed.ts index 287d1931c..ea738b689 100644 --- a/shared/types/Feed.ts +++ b/shared/types/Feed.ts @@ -27,6 +27,8 @@ export interface Rule { match: string; // Regular expression to exclude items. exclude: string; + // Custom script to select if the item should be downloaded (exit with status 80 to download). + script: string; // Destination path where matched items are downloaded to. destination: string; // Tags to be added when items are queued for download.