mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-05 13:20:20 -05:00
@@ -16,13 +16,19 @@ interface BlocklistDetailsModalProps {
|
||||
protocol: DownloadProtocol;
|
||||
indexer?: string;
|
||||
message?: string;
|
||||
source?: string;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function BlocklistDetailsModal(props: BlocklistDetailsModalProps) {
|
||||
const { isOpen, sourceTitle, protocol, indexer, message, onModalClose } =
|
||||
props;
|
||||
|
||||
function BlocklistDetailsModal({
|
||||
isOpen,
|
||||
sourceTitle,
|
||||
protocol,
|
||||
indexer,
|
||||
message,
|
||||
source,
|
||||
onModalClose,
|
||||
}: BlocklistDetailsModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
@@ -50,6 +56,9 @@ function BlocklistDetailsModal(props: BlocklistDetailsModalProps) {
|
||||
data={message}
|
||||
/>
|
||||
) : null}
|
||||
{source ? (
|
||||
<DescriptionListItem title={translate('Source')} data={source} />
|
||||
) : null}
|
||||
</DescriptionList>
|
||||
</ModalBody>
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ function BlocklistRow(props: BlocklistRowProps) {
|
||||
protocol,
|
||||
indexer,
|
||||
message,
|
||||
source,
|
||||
isSelected,
|
||||
columns,
|
||||
onSelectedChange,
|
||||
@@ -154,6 +155,7 @@ function BlocklistRow(props: BlocklistRowProps) {
|
||||
protocol={protocol}
|
||||
indexer={indexer}
|
||||
message={message}
|
||||
source={source}
|
||||
onModalClose={handleDetailsModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
|
||||
@@ -174,7 +174,7 @@ function HistoryDetails(props: HistoryDetailsProps) {
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFailed') {
|
||||
const { message, indexer } = data as DownloadFailedHistory;
|
||||
const { indexer, message, source } = data as DownloadFailedHistory;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
@@ -195,6 +195,10 @@ function HistoryDetails(props: HistoryDetailsProps) {
|
||||
{message ? (
|
||||
<DescriptionListItem title={translate('Message')} data={message} />
|
||||
) : null}
|
||||
|
||||
{source ? (
|
||||
<DescriptionListItem title={translate('Source')} data={source} />
|
||||
) : null}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ function useApiMutation<T, TData>(options: MutationOptions<T, TData>) {
|
||||
headers: {
|
||||
...options.headers,
|
||||
'X-Api-Key': window.Sonarr.apiKey,
|
||||
'X-Sonarr-Client': 'Sonarr',
|
||||
},
|
||||
};
|
||||
}, [options]);
|
||||
|
||||
@@ -26,6 +26,7 @@ const useApiQuery = <T>(options: QueryOptions<T>) => {
|
||||
headers: {
|
||||
...options.headers,
|
||||
'X-Api-Key': window.Sonarr.apiKey,
|
||||
'X-Sonarr-Client': 'Sonarr',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -64,6 +64,7 @@ const usePagedApiQuery = <T>(options: PagedQueryOptions<T>) => {
|
||||
headers: {
|
||||
...options.headers,
|
||||
'X-Api-Key': window.Sonarr.apiKey,
|
||||
'X-Sonarr-Client': 'Sonarr',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ interface Blocklist extends ModelBase {
|
||||
seriesId?: number;
|
||||
indexer?: string;
|
||||
message?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export default Blocklist;
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface GrabbedHistoryData {
|
||||
export interface DownloadFailedHistory {
|
||||
message: string;
|
||||
indexer?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface DownloadFolderImportedHistory {
|
||||
|
||||
23
src/NzbDrone.Common.Test/Http/UserAgentParserFixture.cs
Normal file
23
src/NzbDrone.Common.Test/Http/UserAgentParserFixture.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http;
|
||||
|
||||
[TestFixture]
|
||||
public class UserAgentParserFixture : TestBase
|
||||
{
|
||||
// Ref *Arr `_userAgent = $"{BuildInfo.AppName}/{BuildInfo.Version} ({osName} {osVersion})";`
|
||||
// Ref Mylar `Mylar3/' +str(hash) +'(' +vers +') +http://www.github.com/mylar3/mylar3/`
|
||||
[TestCase("Mylar3/ 3ee23rh23irqfq (13123123) http://www.github.com/mylar3/mylar3/", "Mylar3")]
|
||||
[TestCase("Lidarr/1.0.0.2300 (ubuntu 20.04)", "Lidarr")]
|
||||
[TestCase("Radarr/1.0.0.2300 (ubuntu 20.04)", "Radarr")]
|
||||
[TestCase("Readarr/1.0.0.2300 (ubuntu 20.04)", "Readarr")]
|
||||
[TestCase("Sonarr/3.0.6.9999 (ubuntu 20.04)", "Sonarr")]
|
||||
[TestCase("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", "Other")]
|
||||
public void should_parse_user_agent(string userAgent, string parsedAgent)
|
||||
{
|
||||
UserAgentParser.ParseSource(userAgent).Should().Be(parsedAgent);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NzbDrone.Common.Http
|
||||
{
|
||||
public static class UserAgentParser
|
||||
{
|
||||
private static readonly Regex AppSourceRegex = new(@"(?<agent>[a-z0-9]*)\/.*(?:\(.*\))?",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public static string SimplifyUserAgent(string userAgent)
|
||||
{
|
||||
if (userAgent == null || userAgent.StartsWith("Mozilla/5.0"))
|
||||
@@ -11,5 +16,17 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public static string ParseSource(string userAgent)
|
||||
{
|
||||
var match = AppSourceRegex.Match(SimplifyUserAgent(userAgent) ?? string.Empty);
|
||||
|
||||
if (match.Groups["agent"].Success)
|
||||
{
|
||||
return match.Groups["agent"].Value;
|
||||
}
|
||||
|
||||
return "Other";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
public IndexerFlags IndexerFlags { get; set; }
|
||||
public ReleaseType ReleaseType { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string TorrentInfoHash { get; set; }
|
||||
public List<Language> Languages { get; set; }
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
bool Blocklisted(int seriesId, ReleaseInfo release);
|
||||
bool BlocklistedTorrentHash(int seriesId, string hash);
|
||||
PagingSpec<Blocklist> Paged(PagingSpec<Blocklist> pagingSpec);
|
||||
void Block(RemoteEpisode remoteEpisode, string message);
|
||||
void Block(RemoteEpisode remoteEpisode, string message, string source);
|
||||
void Delete(int id);
|
||||
void Delete(List<int> ids);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
return _blocklistRepository.GetPaged(pagingSpec);
|
||||
}
|
||||
|
||||
public void Block(RemoteEpisode remoteEpisode, string message)
|
||||
public void Block(RemoteEpisode remoteEpisode, string message, string source)
|
||||
{
|
||||
var blocklist = new Blocklist
|
||||
{
|
||||
@@ -85,6 +85,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
Indexer = remoteEpisode.Release.Indexer,
|
||||
Protocol = remoteEpisode.Release.DownloadProtocol,
|
||||
Message = message,
|
||||
Source = source,
|
||||
Languages = remoteEpisode.ParsedEpisodeInfo.Languages
|
||||
};
|
||||
|
||||
@@ -185,6 +186,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||
Indexer = message.Data.GetValueOrDefault("indexer"),
|
||||
Protocol = (DownloadProtocol)Convert.ToInt32(message.Data.GetValueOrDefault("protocol")),
|
||||
Message = message.Message,
|
||||
Source = message.Source,
|
||||
Languages = message.Languages,
|
||||
TorrentInfoHash = message.TrackedDownload?.Protocol == DownloadProtocol.Torrent
|
||||
? message.TrackedDownload.DownloadItem.DownloadId
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(223)]
|
||||
public class add_source_to_blocklist : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Blocklist").AddColumn("Source").AsString().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace NzbDrone.Core.Download
|
||||
public string DownloadClient { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string Source { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
public TrackedDownload TrackedDownload { get; set; }
|
||||
public List<Language> Languages { get; set; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.History;
|
||||
@@ -11,8 +12,8 @@ namespace NzbDrone.Core.Download
|
||||
{
|
||||
public interface IFailedDownloadService
|
||||
{
|
||||
void MarkAsFailed(int historyId, bool skipRedownload = false);
|
||||
void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false);
|
||||
void MarkAsFailed(int historyId, string message = null, string source = null, bool skipRedownload = false);
|
||||
void MarkAsFailed(TrackedDownload trackedDownload, string message = null, string source = null, bool skipRedownload = false);
|
||||
void Check(TrackedDownload trackedDownload);
|
||||
void ProcessFailed(TrackedDownload trackedDownload);
|
||||
}
|
||||
@@ -30,15 +31,16 @@ namespace NzbDrone.Core.Download
|
||||
_eventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
public void MarkAsFailed(int historyId, bool skipRedownload = false)
|
||||
public void MarkAsFailed(int historyId, string message, string source = null, bool skipRedownload = false)
|
||||
{
|
||||
var history = _historyService.Get(historyId);
|
||||
message ??= "Manually marked as failed";
|
||||
|
||||
var history = _historyService.Get(historyId);
|
||||
var downloadId = history.DownloadId;
|
||||
|
||||
if (downloadId.IsNullOrWhiteSpace())
|
||||
{
|
||||
PublishDownloadFailedEvent(history, new List<int> { history.EpisodeId }, "Manually marked as failed", skipRedownload: skipRedownload);
|
||||
PublishDownloadFailedEvent(history, new List<int> { history.EpisodeId }, message, source, skipRedownload: skipRedownload);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -55,16 +57,16 @@ namespace NzbDrone.Core.Download
|
||||
grabbedHistory.AddRange(GetGrabbedHistory(downloadId));
|
||||
grabbedHistory = grabbedHistory.DistinctBy(h => h.Id).ToList();
|
||||
|
||||
PublishDownloadFailedEvent(history, GetEpisodeIds(grabbedHistory), "Manually marked as failed");
|
||||
PublishDownloadFailedEvent(history, GetEpisodeIds(grabbedHistory), message, source);
|
||||
}
|
||||
|
||||
public void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false)
|
||||
public void MarkAsFailed(TrackedDownload trackedDownload, string message, string source = null, bool skipRedownload = false)
|
||||
{
|
||||
var history = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
|
||||
|
||||
if (history.Any())
|
||||
{
|
||||
PublishDownloadFailedEvent(history.First(), GetEpisodeIds(history), "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
|
||||
PublishDownloadFailedEvent(history.First(), GetEpisodeIds(history), message ?? "Manually marked as failed", source, trackedDownload, skipRedownload: skipRedownload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,10 +119,10 @@ namespace NzbDrone.Core.Download
|
||||
}
|
||||
|
||||
trackedDownload.State = TrackedDownloadState.Failed;
|
||||
PublishDownloadFailedEvent(grabbedItems.First(), GetEpisodeIds(grabbedItems), failure, trackedDownload);
|
||||
PublishDownloadFailedEvent(grabbedItems.First(), GetEpisodeIds(grabbedItems), failure, $"{BuildInfo.AppName} Failed Download Handling", trackedDownload);
|
||||
}
|
||||
|
||||
private void PublishDownloadFailedEvent(EpisodeHistory historyItem, List<int> episodeIds, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
|
||||
private void PublishDownloadFailedEvent(EpisodeHistory historyItem, List<int> episodeIds, string message, string source, TrackedDownload trackedDownload = null, bool skipRedownload = false)
|
||||
{
|
||||
Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource);
|
||||
|
||||
@@ -133,6 +135,7 @@ namespace NzbDrone.Core.Download
|
||||
DownloadClient = historyItem.Data.GetValueOrDefault(EpisodeHistory.DOWNLOAD_CLIENT),
|
||||
DownloadId = historyItem.DownloadId,
|
||||
Message = message,
|
||||
Source = source,
|
||||
Data = historyItem.Data,
|
||||
TrackedDownload = trackedDownload,
|
||||
Languages = historyItem.Languages,
|
||||
|
||||
@@ -249,6 +249,7 @@ namespace NzbDrone.Core.History
|
||||
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||
history.Data.Add("DownloadClientName", message.TrackedDownload?.DownloadItem.DownloadClientInfo.Name);
|
||||
history.Data.Add("Message", message.Message);
|
||||
history.Data.Add("Source", message.Source);
|
||||
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteEpisode?.ParsedEpisodeInfo?.ReleaseGroup ?? message.Data.GetValueOrDefault(EpisodeHistory.RELEASE_GROUP));
|
||||
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString() ?? message.Data.GetValueOrDefault(EpisodeHistory.SIZE));
|
||||
history.Data.Add("Indexer", message.TrackedDownload?.RemoteEpisode?.Release?.Indexer ?? message.Data.GetValueOrDefault(EpisodeHistory.INDEXER));
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace Sonarr.Api.V3.Queue
|
||||
{
|
||||
if (blocklist)
|
||||
{
|
||||
_blocklistService.Block(pendingRelease.RemoteEpisode, "Pending release manually blocklisted");
|
||||
_blocklistService.Block(pendingRelease.RemoteEpisode, "Pending release manually blocklisted", null);
|
||||
}
|
||||
|
||||
_pendingReleaseService.RemovePendingQueueItemsObsolete(pendingRelease.Id);
|
||||
@@ -356,7 +356,7 @@ namespace Sonarr.Api.V3.Queue
|
||||
|
||||
if (blocklist)
|
||||
{
|
||||
_failedDownloadService.MarkAsFailed(trackedDownload, skipRedownload);
|
||||
_failedDownloadService.MarkAsFailed(trackedDownload, null, null, skipRedownload);
|
||||
}
|
||||
|
||||
if (!removeFromClient && !blocklist && !changeCategory)
|
||||
|
||||
@@ -68,13 +68,13 @@ namespace Sonarr.Api.V5.Queue
|
||||
}
|
||||
|
||||
[RestDeleteById]
|
||||
public ActionResult RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false, bool changeCategory = false)
|
||||
public ActionResult RemoveAction(int id, string? message = null, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false, bool changeCategory = false)
|
||||
{
|
||||
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
||||
|
||||
if (pendingRelease != null)
|
||||
{
|
||||
Remove(pendingRelease, blocklist);
|
||||
Remove(pendingRelease, message, blocklist);
|
||||
|
||||
return Deleted();
|
||||
}
|
||||
@@ -86,14 +86,14 @@ namespace Sonarr.Api.V5.Queue
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload, changeCategory);
|
||||
Remove(trackedDownload, message, removeFromClient, blocklist, skipRedownload, changeCategory);
|
||||
_trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId);
|
||||
|
||||
return Deleted();
|
||||
}
|
||||
|
||||
[HttpDelete("bulk")]
|
||||
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false, [FromQuery] bool changeCategory = false)
|
||||
public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] string? message, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false, [FromQuery] bool changeCategory = false)
|
||||
{
|
||||
var trackedDownloadIds = new List<string>();
|
||||
var pendingToRemove = new List<NzbDrone.Core.Queue.Queue>();
|
||||
@@ -119,12 +119,12 @@ namespace Sonarr.Api.V5.Queue
|
||||
|
||||
foreach (var pendingRelease in pendingToRemove.DistinctBy(p => p.Id))
|
||||
{
|
||||
Remove(pendingRelease, blocklist);
|
||||
Remove(pendingRelease, message, blocklist);
|
||||
}
|
||||
|
||||
foreach (var trackedDownload in trackedToRemove.DistinctBy(t => t.DownloadItem.DownloadId))
|
||||
{
|
||||
Remove(trackedDownload, removeFromClient, blocklist, skipRedownload, changeCategory);
|
||||
Remove(trackedDownload, message, removeFromClient, blocklist, skipRedownload, changeCategory);
|
||||
trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId);
|
||||
}
|
||||
|
||||
@@ -314,17 +314,17 @@ namespace Sonarr.Api.V5.Queue
|
||||
}
|
||||
}
|
||||
|
||||
private void Remove(NzbDrone.Core.Queue.Queue pendingRelease, bool blocklist)
|
||||
private void Remove(NzbDrone.Core.Queue.Queue pendingRelease, string? message, bool blocklist)
|
||||
{
|
||||
if (blocklist)
|
||||
{
|
||||
_blocklistService.Block(pendingRelease.RemoteEpisode, "Pending release manually blocklisted");
|
||||
_blocklistService.Block(pendingRelease.RemoteEpisode, message ?? "Pending release manually blocklisted", Request.GetSource());
|
||||
}
|
||||
|
||||
_pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id);
|
||||
}
|
||||
|
||||
private TrackedDownload? Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist, bool skipRedownload, bool changeCategory)
|
||||
private TrackedDownload? Remove(TrackedDownload trackedDownload, string? message, bool removeFromClient, bool blocklist, bool skipRedownload, bool changeCategory)
|
||||
{
|
||||
if (removeFromClient)
|
||||
{
|
||||
@@ -351,7 +351,7 @@ namespace Sonarr.Api.V5.Queue
|
||||
|
||||
if (blocklist)
|
||||
{
|
||||
_failedDownloadService.MarkAsFailed(trackedDownload, skipRedownload);
|
||||
_failedDownloadService.MarkAsFailed(trackedDownload, message, Request.GetSource(), skipRedownload);
|
||||
}
|
||||
|
||||
if (!removeFromClient && !blocklist && !changeCategory)
|
||||
|
||||
@@ -97,6 +97,16 @@ namespace Sonarr.Http.Extensions
|
||||
return remoteIP.ToString();
|
||||
}
|
||||
|
||||
public static string GetSource(this HttpRequest request)
|
||||
{
|
||||
if (request.Headers.TryGetValue("X-Sonarr-Client", out var source))
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
return NzbDrone.Common.Http.UserAgentParser.ParseSource(request.Headers["User-Agent"]);
|
||||
}
|
||||
|
||||
public static void DisableCache(this IHeaderDictionary headers)
|
||||
{
|
||||
headers.Remove("Last-Modified");
|
||||
|
||||
Reference in New Issue
Block a user