Add custom scripts for rss download

This commit is contained in:
2023-10-08 22:33:44 +02:00
parent b32abb6615
commit 875ae36d17
7 changed files with 102 additions and 61 deletions

View File

@@ -220,6 +220,7 @@ describe('PUT /api/feed-monitor/rules', () => {
feedIDs: [''],
match: '',
exclude: '.*',
script: '',
destination: tempDirectory,
tags: ['FeedItem'],
startOnLoad: false,

View File

@@ -70,6 +70,7 @@ class FeedService extends BaseService<Record<string, never>> {
field: rule.field,
match: rule.match,
exclude: rule.exclude,
script: rule.script,
startOnLoad: rule.startOnLoad,
isBasePath: rule.isBasePath,
});
@@ -259,22 +260,22 @@ class FeedService extends BaseService<Record<string, never>> {
}
handleNewItems = (feedReaderOptions: FeedReaderOptions, feedItems: Array<FeedItem>): void => {
this.getPreviouslyMatchedUrls()
.then((previouslyMatchedUrls) => {
const {feedID, feedLabel} = feedReaderOptions;
const applicableRules = this.rules[feedID];
if (!applicableRules) return;
this.getPreviouslyMatchedUrls().then(async (previouslyMatchedUrls) => {
const {feedID, feedLabel} = feedReaderOptions;
const applicableRules = this.rules[feedID];
if (!applicableRules) return;
const itemsMatchingRules = getFeedItemsMatchingRules(feedItems, applicableRules);
const itemsToDownload = itemsMatchingRules.filter((item) =>
item.urls.some((url) => !previouslyMatchedUrls.includes(url)),
);
const itemsMatchingRules = await getFeedItemsMatchingRules(feedItems, applicableRules);
const itemsToDownload = itemsMatchingRules.filter((item) =>
item.urls.some((url) => !previouslyMatchedUrls.includes(url)),
);
if (itemsToDownload.length === 0) {
return;
}
if (itemsToDownload.length === 0) {
return;
}
Promise.all(
try {
const ArrayOfURLArrays = await Promise.all(
itemsToDownload.map(async (item): Promise<Array<string>> => {
const {urls, destination, start, tags, ruleID} = item;
@@ -298,28 +299,29 @@ class FeedService extends BaseService<Record<string, never>> {
return urls;
}),
).then((ArrayOfURLArrays) => {
const addedURLs = ArrayOfURLArrays.reduce(
(URLArray: Array<string>, urls: Array<string>) => URLArray.concat(urls),
[],
);
);
const addedURLs = ArrayOfURLArrays.reduce(
(URLArray: Array<string>, urls: Array<string>) => URLArray.concat(urls),
[],
);
this.db.update({type: 'matchedTorrents'}, {$push: {urls: {$each: addedURLs}}}, {upsert: true});
this.db.update({type: 'matchedTorrents'}, {$push: {urls: {$each: addedURLs}}}, {upsert: true});
this.services?.notificationService.addNotification(
itemsToDownload.map((item) => ({
id: 'notification.feed.torrent.added',
data: {
title: item.matchTitle,
feedLabel,
ruleLabel: item.ruleLabel,
},
})),
);
this.services?.torrentService.fetchTorrentList();
});
})
.catch(console.error);
this.services?.notificationService.addNotification(
itemsToDownload.map((item) => ({
id: 'notification.feed.torrent.added',
data: {
title: item.matchTitle,
feedLabel,
ruleLabel: item.ruleLabel,
},
})),
);
this.services?.torrentService.fetchTorrentList();
} catch (e) {
console.error(e);
}
});
};
async removeItem(id: string): Promise<void> {

View File

@@ -1,3 +1,5 @@
import {spawn} from 'node:child_process';
import type {FeedItem} from 'feedsub';
import type {AddTorrentByURLOptions} from '../../shared/schema/api/torrents';
@@ -53,36 +55,58 @@ export const getTorrentUrlsFromFeedItem = (feedItem: FeedItem): Array<string> =>
return [];
};
export const getFeedItemsMatchingRules = (
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 = async (
feedItems: Array<FeedItem>,
rules: Array<Rule>,
): Array<PendingDownloadItems> => {
return feedItems.reduce((matchedItems: Array<PendingDownloadItems>, feedItem) => {
rules.forEach((rule) => {
const matchField = rule.field ? (feedItem[rule.field] as string) : (feedItem.title as string);
const isMatched = new RegExp(rule.match, 'gi').test(matchField);
const isExcluded = rule.exclude !== '' && new RegExp(rule.exclude, 'gi').test(matchField);
): Promise<Array<PendingDownloadItems>> => {
const matchedItems: Array<PendingDownloadItems> = [];
if (isMatched && !isExcluded) {
const torrentUrls = getTorrentUrlsFromFeedItem(feedItem);
const isAlreadyDownloaded = matchedItems.some((matchedItem) =>
torrentUrls.every((url) => matchedItem.urls.includes(url)),
);
await Promise.all(
feedItems.map(async (feedItem) => {
await Promise.all(
rules.map(async (rule) => {
const matchField = rule.field ? (feedItem[rule.field] as string) : (feedItem.title as string);
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, matchField)) === 80;
if (!isAlreadyDownloaded && torrentUrls[0] != null) {
matchedItems.push({
urls: torrentUrls as [string, ...string[]],
tags: rule.tags,
matchTitle: feedItem.title as string,
ruleID: rule._id,
ruleLabel: rule.label,
destination: rule.destination,
start: rule.startOnLoad,
});
}
}
});
if (isMatched && !isExcluded && scriptMatch) {
const torrentUrls = getTorrentUrlsFromFeedItem(feedItem);
const isAlreadyDownloaded = matchedItems.some((matchedItem) =>
torrentUrls.every((url) => matchedItem.urls.includes(url)),
);
return matchedItems;
}, []);
if (!isAlreadyDownloaded && torrentUrls[0] != null) {
matchedItems.push({
urls: torrentUrls as [string, ...string[]],
tags: rule.tags,
matchTitle: feedItem.title as string,
ruleID: rule._id,
ruleLabel: rule.label,
destination: rule.destination,
start: rule.startOnLoad,
});
}
}
}),
);
}),
);
return matchedItems;
};