New: Option to prefer magnet URLs over torrent file links

Co-authored-by: Deathspike <meister.deathspike@outlook.com>

New: Bulk edit Prefer Magnet Url for indexers
This commit is contained in:
Bogdan
2024-09-11 14:55:28 +03:00
parent a32ab3acfd
commit 4e8b9e81cf
13 changed files with 97 additions and 6 deletions

View File

@@ -19,6 +19,7 @@ interface SavePayload {
seedRatio?: number;
seedTime?: number;
packSeedTime?: number;
preferMagnetUrl?: boolean;
}
interface EditIndexerModalContentProps {
@@ -65,6 +66,9 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
const [packSeedTime, setPackSeedTime] = useState<null | string | number>(
null
);
const [preferMagnetUrl, setPreferMagnetUrl] = useState<
null | string | boolean
>(null);
const save = useCallback(() => {
let hasChanges = false;
@@ -105,6 +109,11 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
payload.packSeedTime = packSeedTime as number;
}
if (preferMagnetUrl !== null) {
hasChanges = true;
payload.preferMagnetUrl = preferMagnetUrl === 'true';
}
if (hasChanges) {
onSavePress(payload);
}
@@ -118,6 +127,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
seedRatio,
seedTime,
packSeedTime,
preferMagnetUrl,
onSavePress,
onModalClose,
]);
@@ -146,6 +156,9 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
case 'packSeedTime':
setPackSeedTime(value);
break;
case 'preferMagnetUrl':
setPreferMagnetUrl(value);
break;
default:
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
@@ -254,6 +267,18 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
onChange={onInputChange}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('PreferMagnetUrl')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="preferMagnetUrl"
value={preferMagnetUrl}
values={enableOptions}
onChange={onInputChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter className={styles.modalFooter}>

View File

@@ -29,7 +29,8 @@
.minimumSeeders,
.seedRatio,
.seedTime,
.packSeedTime {
.packSeedTime,
.preferMagnetUrl {
composes: cell;
flex: 0 0 90px;

View File

@@ -11,6 +11,7 @@ interface CssExports {
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
'preferMagnetUrl': string;
'priority': string;
'privacy': string;
'protocol': string;

View File

@@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
import CheckInput from 'Components/Form/CheckInput';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
@@ -74,6 +75,10 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
fields.find((field) => field.name === 'torrentBaseSettings.packSeedTime')
?.value ?? undefined;
const preferMagnetUrl =
fields.find((field) => field.name === 'torrentBaseSettings.preferMagnetUrl')
?.value ?? undefined;
const rssUrl = `${window.location.origin}${
window.Prowlarr.urlBase
}/${id}/api?apikey=${encodeURIComponent(
@@ -102,6 +107,10 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
setIsDeleteIndexerModalOpen(false);
}, [setIsDeleteIndexerModalOpen]);
const checkInputCallback = useCallback(() => {
// Mock handler to satisfy `onChange` being required for `CheckInput`.
}, []);
const onSelectedChange = useCallback(
({ id, value, shiftKey }: SelectStateInputProps) => {
selectDispatch({
@@ -277,6 +286,21 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
);
}
if (name === 'preferMagnetUrl') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>
{preferMagnetUrl === undefined ? null : (
<CheckInput
name="preferMagnetUrl"
value={preferMagnetUrl}
isDisabled={true}
onChange={checkInputCallback}
/>
)}
</VirtualTableRowCell>
);
}
if (name === 'actions') {
return (
<VirtualTableRowCell

View File

@@ -22,7 +22,8 @@
.minimumSeeders,
.seedRatio,
.seedTime,
.packSeedTime {
.packSeedTime,
.preferMagnetUrl {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 90px;

View File

@@ -8,6 +8,7 @@ interface CssExports {
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
'preferMagnetUrl': string;
'priority': string;
'privacy': string;
'protocol': string;

View File

@@ -116,6 +116,26 @@ export const sortPredicates = {
vipExpiration: function({ fields = [] }) {
return fields.find((field) => field.name === 'vipExpiration')?.value ?? '';
},
minimumSeeders: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.appMinimumSeeders')?.value ?? undefined;
},
seedRatio: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.seedRatio')?.value ?? undefined;
},
seedTime: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.seedTime')?.value ?? undefined;
},
packSeedTime: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.packSeedTime')?.value ?? undefined;
},
preferMagnetUrl: function({ fields = [] }) {
return fields.find((field) => field.name === 'torrentBaseSettings.preferMagnetUrl')?.value ?? undefined;
}
};

View File

@@ -116,6 +116,12 @@ export const defaultState = {
isSortable: true,
isVisible: false
},
{
name: 'preferMagnetUrl',
label: () => translate('PreferMagnetUrl'),
isSortable: true,
isVisible: false
},
{
name: 'tags',
label: () => translate('Tags'),

View File

@@ -54,7 +54,7 @@ namespace NzbDrone.Core.IndexerSearch
return new XElement(feedNamespace + "attr", new XAttribute("name", name), new XAttribute("value", value));
}
public string ToXml(DownloadProtocol protocol)
public string ToXml(DownloadProtocol protocol, bool preferMagnetUrl = false)
{
// IMPORTANT: We can't use Uri.ToString(), because it generates URLs without URL encode (links with unicode
// characters are broken). We must use Uri.AbsoluteUri instead that handles encoding correctly
@@ -73,6 +73,7 @@ namespace NzbDrone.Core.IndexerSearch
new XElement("title", "Prowlarr"),
from r in Releases
let t = (r as TorrentInfo) ?? new TorrentInfo()
let downloadUrl = preferMagnetUrl ? t.MagnetUrl ?? r.DownloadUrl : r.DownloadUrl ?? t.MagnetUrl
select new XElement("item",
new XElement("title", RemoveInvalidXMLChars(r.Title)),
new XElement("description", RemoveInvalidXMLChars(r.Description)),
@@ -85,11 +86,11 @@ namespace NzbDrone.Core.IndexerSearch
r.InfoUrl == null ? null : new XElement("comments", r.InfoUrl),
r.PublishDate == DateTime.MinValue ? new XElement("pubDate", XmlDateFormat(DateTime.Now)) : new XElement("pubDate", XmlDateFormat(r.PublishDate)),
new XElement("size", r.Size),
new XElement("link", r.DownloadUrl ?? t.MagnetUrl ?? string.Empty),
new XElement("link", downloadUrl ?? string.Empty),
r.Categories == null ? null : from c in r.Categories select new XElement("category", c.Id),
new XElement(
"enclosure",
new XAttribute("url", r.DownloadUrl ?? t.MagnetUrl ?? string.Empty),
new XAttribute("url", downloadUrl ?? string.Empty),
r.Size == null ? null : new XAttribute("length", r.Size),
new XAttribute("type", protocol == DownloadProtocol.Torrent ? "application/x-bittorrent" : "application/x-nzb")),
r.Categories == null ? null : from c in r.Categories select GetNabElement("category", c.Id, protocol),

View File

@@ -63,5 +63,8 @@ namespace NzbDrone.Core.Indexers
[FieldDefinition(4, Type = FieldType.Number, Label = "IndexerSettingsPackSeedTime", HelpText = "IndexerSettingsPackSeedTimeIndexerHelpText", Unit = "minutes", Advanced = true)]
public int? PackSeedTime { get; set; }
[FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsPreferMagnetUrl", HelpText = "IndexerSettingsPreferMagnetUrlHelpText", Advanced = true)]
public bool PreferMagnetUrl { get; set; }
}
}

View File

@@ -407,6 +407,8 @@
"IndexerSettingsPackSeedTime": "Pack Seed Time",
"IndexerSettingsPackSeedTimeIndexerHelpText": "The time a pack (season or discography) torrent should be seeded before stopping, empty is app's default",
"IndexerSettingsPasskey": "Pass Key",
"IndexerSettingsPreferMagnetUrl": "Prefer Magnet URL",
"IndexerSettingsPreferMagnetUrlHelpText": "When enabled, this indexer will prefer the use of magnet URLs for grabs with fallback to torrent links",
"IndexerSettingsQueryLimit": "Query Limit",
"IndexerSettingsQueryLimitHelpText": "The number of max queries as specified by the respective unit that {appName} will allow to the site",
"IndexerSettingsRssKey": "RSS Key",
@@ -541,6 +543,8 @@
"PendingChangesStayReview": "Stay and review changes",
"Port": "Port",
"PortNumber": "Port Number",
"PreferMagnetUrl": "Prefer Magnet URL",
"PreferMagnetUrlHelpText": "When enabled, this indexer will prefer the use of magnet URLs for grabs with fallback to torrent links",
"Presets": "Presets",
"Priority": "Priority",
"PrioritySettings": "Priority: {priority}",

View File

@@ -12,6 +12,7 @@ namespace Prowlarr.Api.V1.Indexers
public double? SeedRatio { get; set; }
public int? SeedTime { get; set; }
public int? PackSeedTime { get; set; }
public bool? PreferMagnetUrl { get; set; }
}
public class IndexerBulkResourceMapper : ProviderBulkResourceMapper<IndexerBulkResource, IndexerDefinition>
@@ -35,6 +36,7 @@ namespace Prowlarr.Api.V1.Indexers
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedRatio = resource.SeedRatio ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedRatio;
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedTime = resource.SeedTime ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.SeedTime;
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.PackSeedTime = resource.PackSeedTime ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.PackSeedTime;
((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.PreferMagnetUrl = resource.PreferMagnetUrl ?? ((ITorrentIndexerSettings)existing.Settings).TorrentBaseSettings.PreferMagnetUrl;
}
});

View File

@@ -198,7 +198,9 @@ namespace NzbDrone.Api.V1.Indexers
}
}
return CreateResponse(results.ToXml(indexer.Protocol));
var preferMagnetUrl = indexer.Protocol == DownloadProtocol.Torrent && indexerDef.Settings is ITorrentIndexerSettings torrentIndexerSettings && (torrentIndexerSettings.TorrentBaseSettings?.PreferMagnetUrl ?? false);
return CreateResponse(results.ToXml(indexer.Protocol, preferMagnetUrl));
default:
return CreateResponse(CreateErrorXML(202, $"No such function ({requestType})"), statusCode: StatusCodes.Status400BadRequest);
}