diff --git a/.devcontainer/Sonarr.code-workspace b/.devcontainer/Sonarr.code-workspace new file mode 100644 index 000000000..a46158e44 --- /dev/null +++ b/.devcontainer/Sonarr.code-workspace @@ -0,0 +1,13 @@ +// This file is used to open the backend and frontend in the same workspace, which is necessary as +// the frontend has vscode settings that are distinct from the backend +{ + "folders": [ + { + "path": ".." + }, + { + "path": "../frontend" + } + ], + "settings": {} +} diff --git a/.github/labeler.yml b/.github/labeler.yml index 3b42128d4..fdd66d11a 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,17 +1,23 @@ 'connection': - - src/NzbDrone.Core/Notifications/**/* + - changed-files: + - any-glob-to-any-file: src/NzbDrone.Core/Notifications/**/* 'db-migration': - - src/NzbDrone.Core/Datastore/Migration/* + - changed-files: + - any-glob-to-any-file: src/NzbDrone.Core/Datastore/Migration/* 'download-client': - - src/NzbDrone.Core/Download/Clients/**/* + - changed-files: + - any-glob-to-any-file: src/NzbDrone.Core/Download/Clients/**/* 'indexer': - - src/NzbDrone.Core/Indexers/**/* + - changed-files: + - any-glob-to-any-file: src/NzbDrone.Core/Indexers/**/* 'parsing': - - src/NzbDrone.Core/Parser/**/* + - changed-files: + - any-glob-to-any-file: src/NzbDrone.Core/Parser/**/* 'ui-only': - - all: ['frontend/**/*'] + - changed-files: + - any-glob-to-all-files: frontend/**/* diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e0c23a88c..c73e45a6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -236,7 +236,7 @@ jobs: steps: - name: Notify - uses: tsickert/discord-webhook@v5.3.0 + uses: tsickert/discord-webhook@v6.0.0 with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} username: 'GitHub Actions' diff --git a/.github/workflows/conflict_labeler.yml b/.github/workflows/conflict_labeler.yml new file mode 100644 index 000000000..e9afb71a3 --- /dev/null +++ b/.github/workflows/conflict_labeler.yml @@ -0,0 +1,26 @@ +name: Merge Conflict Labeler + +on: + push: + branches: + - develop + pull_request_target: + branches: + - develop + types: [synchronize] + +jobs: + label: + name: Labeling + runs-on: ubuntu-latest + if: ${{ github.repository == 'Sonarr/Sonarr' }} + permissions: + contents: read + pull-requests: write + steps: + - name: Apply label + uses: eps1lon/actions-label-merge-conflict@v3 + with: + dirtyLabel: 'merge-conflict' + repoToken: '${{ secrets.GITHUB_TOKEN }}' + \ No newline at end of file diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 857cfb4a7..ab2292824 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,4 +9,4 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 0435b1c71..03ec90954 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -9,13 +9,13 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@v5 with: github-token: ${{ github.token }} - issue-lock-inactive-days: '90' - issue-exclude-created-before: '' - issue-exclude-labels: 'one-day-maybe' - issue-lock-labels: '' - issue-lock-comment: '' + issue-inactive-days: '90' + exclude-issue-created-before: '' + exclude-any-issue-labels: 'one-day-maybe' + add-issue-labels: '' + issue-comment: '' issue-lock-reason: 'resolved' process-only: '' diff --git a/frontend/src/Store/Actions/Creators/createTestProviderHandler.js b/frontend/src/Store/Actions/Creators/createTestProviderHandler.js index ca26883fb..e35157dbd 100644 --- a/frontend/src/Store/Actions/Creators/createTestProviderHandler.js +++ b/frontend/src/Store/Actions/Creators/createTestProviderHandler.js @@ -1,8 +1,11 @@ +import $ from 'jquery'; +import _ from 'lodash'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import getProviderState from 'Utilities/State/getProviderState'; import { set } from '../baseActions'; const abortCurrentRequests = {}; +let lastTestData = null; export function createCancelTestProviderHandler(section) { return function(getState, payload, dispatch) { @@ -17,10 +20,25 @@ function createTestProviderHandler(section, url) { return function(getState, payload, dispatch) { dispatch(set({ section, isTesting: true })); - const testData = getProviderState(payload, getState, section); + const { + queryParams = {}, + ...otherPayload + } = payload; + + const testData = getProviderState({ ...otherPayload }, getState, section); + const params = { ...queryParams }; + + // If the user is re-testing the same provider without changes + // force it to be tested. + + if (_.isEqual(testData, lastTestData)) { + params.forceTest = true; + } + + lastTestData = testData; const ajaxOptions = { - url: `${url}/test`, + url: `${url}/test?${$.param(params, true)}`, method: 'POST', contentType: 'application/json', dataType: 'json', @@ -32,6 +50,8 @@ function createTestProviderHandler(section, url) { abortCurrentRequests[section] = abortRequest; request.done((data) => { + lastTestData = null; + dispatch(set({ section, isTesting: false, diff --git a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs index 0ee1716fa..948867108 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NzbDrone.Core.Indexers; using NzbDrone.Core.Validation; @@ -12,5 +13,7 @@ namespace NzbDrone.Core.Test.IndexerTests } public string BaseUrl { get; set; } + + public IEnumerable MultiLanguages { get; set; } } } diff --git a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs index dce7fafc6..a7a363c9f 100644 --- a/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/LanguageParserFixture.cs @@ -429,6 +429,26 @@ namespace NzbDrone.Core.Test.ParserTests result.Languages.Should().Contain(Language.English); } + [TestCase("Остання серія (Сезон 1) / The Last Series (Season 1) (2024) WEB-DLRip-AVC 2xUkr/Eng | Sub Ukr/Eng")] + [TestCase("Справжня серія (Сезон 1-3) / True Series (Season 1-3) (2014-2019) BDRip-AVC 3xUkr/Eng | Ukr/Eng")] + [TestCase("Серія (Сезон 1-3) / The Series (Seasons 1-3) (2019-2022) BDRip-AVC 4xUkr/Eng | Sub 2xUkr/Eng")] + public void should_parse_english_and_ukranian(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Languages.Count.Should().Be(2); + result.Languages.Should().Contain(Language.Ukrainian); + result.Languages.Should().Contain(Language.English); + } + + [TestCase("Серія (Сезон 1, серії 01-26 із 51) / Seri (Season 1, episodes 01-26) (2018) WEBRip-AVC 2Ukr/Tur")] + public void should_parse_turkish_and_ukranian(string postTitle) + { + var result = Parser.Parser.ParseTitle(postTitle); + result.Languages.Count.Should().Be(2); + result.Languages.Should().Contain(Language.Ukrainian); + result.Languages.Should().Contain(Language.Turkish); + } + [TestCase("Name (2020) - S01E20 - [AAC 2.0].testtitle.default.eng.forced.ass", new[] { "default", "forced" }, "testtitle", "English")] [TestCase("Name (2020) - S01E20 - [AAC 2.0].eng.default.testtitle.forced.ass", new[] { "default", "forced" }, "testtitle", "English")] [TestCase("Name (2020) - S01E20 - [AAC 2.0].default.eng.testtitle.forced.ass", new[] { "default", "forced" }, "testtitle", "English")] diff --git a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs index 7f4491a2b..e424a46f8 100644 --- a/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs +++ b/src/NzbDrone.Core/Indexers/BroadcastheNet/BroadcastheNetSettings.cs @@ -1,5 +1,8 @@ +using System; +using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.BroadcastheNet @@ -23,6 +26,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet { BaseUrl = "https://api.broadcasthe.net/"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] @@ -40,6 +44,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet [FieldDefinition(4, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + [FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs b/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs index 51ff19fa1..57abc672e 100644 --- a/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs +++ b/src/NzbDrone.Core/Indexers/Fanzub/FanzubSettings.cs @@ -1,5 +1,8 @@ +using System; +using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.Fanzub @@ -19,6 +22,7 @@ namespace NzbDrone.Core.Indexers.Fanzub public FanzubSettings() { BaseUrl = "http://fanzub.com/rss/"; + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsRssUrl", HelpText = "IndexerSettingsRssUrlHelpText")] @@ -28,6 +32,9 @@ namespace NzbDrone.Core.Indexers.Fanzub [FieldDefinition(1, Label = "IndexerSettingsAnimeStandardFormatSearch", Type = FieldType.Checkbox, HelpText = "IndexerSettingsAnimeStandardFormatSearchHelpText")] public bool AnimeStandardFormatSearch { get; set; } + [FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs index 2ef02c7de..13846a25f 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListSettings.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.FileList @@ -35,6 +36,7 @@ namespace NzbDrone.Core.Indexers.FileList }; AnimeCategories = Array.Empty(); + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)] @@ -43,6 +45,9 @@ namespace NzbDrone.Core.Indexers.FileList [FieldDefinition(1, Label = "IndexerSettingsPasskey", Privacy = PrivacyLevel.ApiKey)] public string Passkey { get; set; } + [FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + [FieldDefinition(3, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] public string BaseUrl { get; set; } diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs index b5833789f..7d90cfa40 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.HDBits @@ -29,6 +30,7 @@ namespace NzbDrone.Core.Indexers.HDBits Categories = new[] { (int)HdBitsCategory.Tv, (int)HdBitsCategory.Documentary }; Codecs = Array.Empty(); Mediums = Array.Empty(); + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")] @@ -58,6 +60,9 @@ namespace NzbDrone.Core.Indexers.HDBits [FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + [FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs index 87e7f03d2..5491b7c52 100644 --- a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs @@ -1,9 +1,12 @@ -using NzbDrone.Core.ThingiProvider; +using System.Collections.Generic; +using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers { public interface IIndexerSettings : IProviderConfig { string BaseUrl { get; set; } + + IEnumerable MultiLanguages { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs index 5c1271459..841c98ebf 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs @@ -1,7 +1,10 @@ +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using FluentValidation; using NzbDrone.Common.Extensions; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.IPTorrents @@ -29,6 +32,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents public IPTorrentsSettings() { MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "IndexerIPTorrentsSettingsFeedUrl", HelpText = "IndexerIPTorrentsSettingsFeedUrlHelpText")] @@ -43,6 +47,9 @@ namespace NzbDrone.Core.Indexers.IPTorrents [FieldDefinition(3, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + [FieldDefinition(4, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index dbb9916c0..4696bea3c 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using FluentValidation.Results; using NLog; +using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Languages; using NzbDrone.Core.Localization; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; @@ -17,6 +20,8 @@ namespace NzbDrone.Core.Indexers public abstract class IndexerBase : IIndexer where TSettings : IIndexerSettings, new() { + private static readonly Regex MultiRegex = new (@"[_. ](?multi)[_. ]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + protected readonly IIndexerStatusService _indexerStatusService; protected readonly IConfigService _configService; protected readonly IParsingService _parsingService; @@ -84,9 +89,16 @@ namespace NzbDrone.Core.Indexers protected virtual IList CleanupReleases(IEnumerable releases) { var result = releases.DistinctBy(v => v.Guid).ToList(); + var settings = Definition.Settings as IIndexerSettings; result.ForEach(c => { + // Use multi languages from setting if ReleaseInfo languages is empty + if (c.Languages.Empty() && MultiRegex.IsMatch(c.Title) && settings.MultiLanguages.Any()) + { + c.Languages = settings.MultiLanguages.Select(i => (Language)i).ToList(); + } + c.IndexerId = Definition.Id; c.Indexer = Definition.Name; c.DownloadProtocol = Protocol; diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index a38229560..b329140ea 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -1,9 +1,11 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using FluentValidation; using NzbDrone.Common.Extensions; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.Newznab @@ -55,6 +57,7 @@ namespace NzbDrone.Core.Indexers.Newznab ApiPath = "/api"; Categories = new[] { 5030, 5040 }; AnimeCategories = Enumerable.Empty(); + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "URL")] @@ -79,7 +82,10 @@ namespace NzbDrone.Core.Indexers.Newznab [FieldDefinition(6, Label = "IndexerSettingsAdditionalParameters", HelpText = "IndexerSettingsAdditionalNewznabParametersHelpText", Advanced = true)] public string AdditionalParameters { get; set; } - // Field 7 is used by TorznabSettings MinimumSeeders + [FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + + // Field 8 is used by TorznabSettings MinimumSeeders // If you need to add another field here, update TorznabSettings as well and this comment public virtual NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs index 6983b2d67..516c34604 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs @@ -1,6 +1,9 @@ +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.Nyaa @@ -25,6 +28,7 @@ namespace NzbDrone.Core.Indexers.Nyaa BaseUrl = ""; AdditionalParameters = "&cats=1_0&filter=1"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")] @@ -45,6 +49,9 @@ namespace NzbDrone.Core.Indexers.Nyaa [FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs index 898442bf7..5b3d4f3ef 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs @@ -1,5 +1,8 @@ +using System; +using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.TorrentRss @@ -23,6 +26,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss BaseUrl = string.Empty; AllowZeroSize = false; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsRssUrl")] @@ -43,6 +47,9 @@ namespace NzbDrone.Core.Indexers.TorrentRss [FieldDefinition(5, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + [FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs index d999d84ba..47713230d 100644 --- a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs @@ -1,5 +1,8 @@ +using System; +using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.Annotations; +using NzbDrone.Core.Languages; using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.Torrentleech @@ -23,6 +26,7 @@ namespace NzbDrone.Core.Indexers.Torrentleech { BaseUrl = "http://rss.torrentleech.org"; MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; + MultiLanguages = Array.Empty(); } [FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")] @@ -40,6 +44,9 @@ namespace NzbDrone.Core.Indexers.Torrentleech [FieldDefinition(4, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } + [FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)] + public IEnumerable MultiLanguages { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs index 6ed534bb2..6a84b59cd 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs @@ -49,13 +49,13 @@ namespace NzbDrone.Core.Indexers.Torznab MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(7, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] + [FieldDefinition(8, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)] public int MinimumSeeders { get; set; } - [FieldDefinition(8)] + [FieldDefinition(9)] public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings(); - [FieldDefinition(9, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] + [FieldDefinition(10, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)] public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; } public override NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Languages/RealLanguageFieldConverter.cs b/src/NzbDrone.Core/Languages/RealLanguageFieldConverter.cs new file mode 100644 index 000000000..daca95472 --- /dev/null +++ b/src/NzbDrone.Core/Languages/RealLanguageFieldConverter.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Annotations; + +namespace NzbDrone.Core.Languages +{ + public class RealLanguageFieldConverter : ISelectOptionsConverter + { + public List GetSelectOptions() + { + return Language.All + .Where(l => l != Language.Unknown) + .OrderBy(l => l.Id > 0).ThenBy(l => l.Name) + .ToList() + .ConvertAll(v => new SelectOption { Value = v.Id, Name = v.Name }); + } + } +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index f6b312928..78b8557e7 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -978,6 +978,8 @@ "IndexerSettingsPasskey": "Passkey", "IndexerSettingsRejectBlocklistedTorrentHashes": "Reject Blocklisted Torrent Hashes While Grabbing", "IndexerSettingsRejectBlocklistedTorrentHashesHelpText": "If a torrent is blocked by hash it may not properly be rejected during RSS/Search for some indexers, enabling this will allow it to be rejected after the torrent is grabbed, but before it is sent to the client.", + "IndexerSettingsMultiLanguageRelease": "Multi Languages", + "IndexerSettingsMultiLanguageReleaseHelpText": "What languages are normally in a multi release on this indexer?", "IndexerSettingsRssUrl": "RSS URL", "IndexerSettingsRssUrlHelpText": "Enter to URL to an {indexer} compatible RSS feed", "IndexerSettingsSeasonPackSeedTime": "Season-Pack Seed Time", diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index bfcedc8d1..975286610 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -68,11 +68,11 @@ "CancelPendingTask": "Êtes-vous sur de vouloir annuler cette tâche en attente ?", "Clear": "Effacer", "AddAutoTagError": "Impossible d'ajouter un nouveau tag automatique, veuillez réessayer.", - "AddConditionError": "Impossible d'ajouter une nouvelle condition, Réessayer.", + "AddConditionError": "Impossible d'ajouter une nouvelle condition, veuillez réessayer.", "AddCondition": "Ajouter une condition", "AddAutoTag": "Ajouter un tag automatique", "AddCustomFormatError": "Impossible d'ajouter un nouveau format personnalisé, veuillez réessayer.", - "AddIndexerError": "Impossible d'ajouter un nouvelle indexeur, veuillez réessayer.", + "AddIndexerError": "Impossible d'ajouter un nouvel indexeur, veuillez réessayer.", "AddNewRestriction": "Ajouter une nouvelle restriction", "AddListError": "Impossible d'ajouter une nouvelle liste, veuillez réessayer.", "AddDownloadClientError": "Impossible d'ajouter un nouveau client de téléchargement, veuillez réessayer.", @@ -534,7 +534,7 @@ "IndexerSearchNoInteractiveHealthCheckMessage": "Aucun indexeur n'est disponible avec la recherche interactive activée. {appName} ne fournira aucun résultat de recherche interactif", "IndexerStatusUnavailableHealthCheckMessage": "Indexeurs indisponibles en raison d'échecs : {indexerNames}", "Info": "Information", - "InstallLatest": "Installer le dernier", + "InstallLatest": "Installer la dernière", "InteractiveImportNoLanguage": "La ou les langues doivent être choisies pour chaque fichier sélectionné", "InteractiveImportNoQuality": "La qualité doit être choisie pour chaque fichier sélectionné", "InteractiveSearchModalHeader": "Recherche interactive", @@ -2066,5 +2066,8 @@ "EpisodeTitleFootNote": "Contrôlez éventuellement la troncature à un nombre maximum d'octets, y compris les points de suspension (`...`). La troncature de la fin (par exemple `{Episode Title:30}`) ou du début (par exemple `{Episode Title:-30}`) sont toutes deux prises en charge. Les titres des épisodes seront automatiquement tronqués en fonction des limitations du système de fichiers si nécessaire.", "SelectReleaseType": "Sélectionnez le type de version", "SeriesFootNote": "Contrôlez éventuellement la troncature à un nombre maximum d'octets, y compris les points de suspension (`...`). La troncature de la fin (par exemple `{Series Title:30}`) ou du début (par exemple `{Series Title:-30}`) sont toutes deux prises en charge.", - "ReleaseGroupFootNote": "Contrôlez éventuellement la troncature à un nombre maximum d'octets, y compris les points de suspension (`...`). La troncature de la fin (par exemple `{Release Group:30}`) ou du début (par exemple `{Release Group:-30}`) sont toutes deux prises en charge.`)." + "ReleaseGroupFootNote": "Contrôlez éventuellement la troncature à un nombre maximum d'octets, y compris les points de suspension (`...`). La troncature de la fin (par exemple `{Release Group:30}`) ou du début (par exemple `{Release Group:-30}`) sont toutes deux prises en charge.`).", + "AutoTaggingSpecificationTag": "Étiquette", + "NotificationsTelegramSettingsIncludeAppName": "Inclure {appName} dans le Titre", + "NotificationsTelegramSettingsIncludeAppNameHelpText": "Préfixer éventuellement le titre du message par {appName} pour différencier les notifications des différentes applications" } diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index 8b6adc4bf..d8113dd53 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -249,5 +249,6 @@ "AnimeEpisodeTypeFormat": "Numero assoluto dell'episodio ({format})", "AutoRedownloadFailed": "Download fallito", "AddDelayProfileError": "Impossibile aggiungere un nuovo profilo di ritardo, riprova.", - "Cutoff": "Taglio" + "Cutoff": "Taglio", + "AddListExclusion": "Aggiungi elenco esclusioni" } diff --git a/src/NzbDrone.Core/Localization/Core/tr.json b/src/NzbDrone.Core/Localization/Core/tr.json index 3a45b393b..adaa32070 100644 --- a/src/NzbDrone.Core/Localization/Core/tr.json +++ b/src/NzbDrone.Core/Localization/Core/tr.json @@ -136,12 +136,90 @@ "AuthenticationMethod": "Kimlik Doğrulama Yöntemi", "AuthenticationRequired": "Kimlik Doğrulama Gerekli", "AuthenticationRequiredWarning": "Kimlik doğrulaması olmadan uzaktan erişimi engellemek için, {appName}'da artık kimlik doğrulamanın etkinleştirilmesini gerektiriyor. İsteğe bağlı olarak yerel adresler için kimlik doğrulamayı devre dışı bırakabilirsiniz.", - "ApiKeyValidationHealthCheckMessage": "Lütfen API anahtarınızı en az {length} karakter uzunluğunda olacak şekilde güncelleyin. Bunu ayarlar veya yapılandırma dosyası aracılığıyla yapabilirsiniz", + "ApiKeyValidationHealthCheckMessage": "Lütfen API anahtarınızı en az {length} karakter sayısı kadar güncelleyiniz. Bunu ayarlar veya yapılandırma dosyası üzerinden yapabilirsiniz", "ClearBlocklistMessageText": "Engellenenler listesindeki tüm öğeleri temizlemek istediğinizden emin misiniz?", "AutomaticUpdatesDisabledDocker": "Docker güncelleme mekanizması kullanıldığında otomatik güncellemeler doğrudan desteklenmez. Kapsayıcı görüntüsünü {appName} dışında güncellemeniz veya bir komut dosyası kullanmanız gerekecek", "ConnectionLostReconnect": "{appName} otomatik bağlanmayı deneyecek veya aşağıda yeniden yükle seçeneğini işaretleyebilirsiniz.", "BlackholeWatchFolderHelpText": "{appName} uygulamasının tamamlanmış indirmeleri içe aktaracağı klasör", "AuthenticationRequiredPasswordConfirmationHelpTextWarning": "Yeni şifreyi onayla", "BindAddressHelpText": "Tüm arayüzler için geçerli IP adresi, localhost veya '*'", - "CloneAutoTag": "Otomatik Etiketi Klonla" + "CloneAutoTag": "Otomatik Etiketi Klonla", + "Dash": "Çizgi", + "DeleteReleaseProfileMessageText": "'{name}' bu sürüm profilini silmek istediğinizden emin misiniz?", + "DownloadClientFreeboxApiError": "Freebox API'si şu hatayı döndürdü: {errorDescription}", + "DeleteSelectedDownloadClients": "İndirme İstemcilerini Sil", + "DeleteSelectedDownloadClientsMessageText": "Seçilen {count} indirme istemcisini silmek istediğinizden emin misiniz?", + "DeleteRootFolderMessageText": "'{path}' kök klasörünü silmek istediğinizden emin misiniz?", + "DeleteSpecificationHelpText": "'{name}' spesifikasyonunu silmek istediğinizden emin misiniz?", + "DeletedReasonUpgrade": "Bir yükseltmeyi içe aktarmak için dosya silindi", + "DelayMinutes": "{delay} Dakika", + "DeleteImportListMessageText": "'{name}' listesini silmek istediğinizden emin misiniz?", + "DeleteReleaseProfile": "Sürüm Profilini Sil", + "DeleteSelectedIndexers": "Dizin Oluşturucuları Sil", + "Directory": "Rehber", + "Donate": "Bağış yap", + "DownloadClientDownloadStationValidationFolderMissing": "Klasör mevcut değil", + "DownloadClientFloodSettingsAdditionalTags": "Ek Etiketler", + "DownloadClientFloodSettingsPostImportTags": "İçe Aktarma Sonrası Etiketler", + "DownloadClientFloodSettingsStartOnAdd": "Eklemeye Başla", + "DownloadClientFloodSettingsTagsHelpText": "Bir indirme işleminin başlangıç etiketleri. Bir indirmenin tanınabilmesi için tüm başlangıç etiketlerine sahip olması gerekir. Bu, ilgisiz indirmelerle çakışmaları önler.", + "DownloadClientAriaSettingsDirectoryHelpText": "İndirilenlerin yerleştirileceği isteğe bağlı konum, varsayılan Aria2 konumunu kullanmak için boş bırakın", + "DefaultNameCopiedProfile": "{name} - Kopyala", + "DeleteAutoTag": "Etiketi Otomatik Sil", + "DeleteCondition": "Koşulu Sil", + "DeleteDelayProfileMessageText": "Bu gecikme profilini silmek istediğinizden emin misiniz?", + "DeleteRootFolder": "Kök Klasörü Sil", + "DeleteSpecification": "Spesifikasyonu Sil", + "DeletedReasonManual": "Dosya, {appName} kullanılarak manuel olarak veya API aracılığıyla başka bir araçla silindi", + "DeleteCustomFormatMessageText": "'{name}' özel biçimini silmek istediğinizden emin misiniz?", + "DefaultNameCopiedSpecification": "{name} - Kopyala", + "DeleteConditionMessageText": "'{name}' koşulunu silmek istediğinizden emin misiniz?", + "DeleteImportListExclusionMessageText": "Bu içe aktarma listesi hariç tutma işlemini silmek istediğinizden emin misiniz?", + "DeleteQualityProfileMessageText": "'{name}' kalite profilini silmek istediğinizden emin misiniz?", + "DeleteSelectedIndexersMessageText": "Seçilen {count} dizin oluşturucuyu silmek istediğinizden emin misiniz?", + "DownloadClientDelugeValidationLabelPluginFailureDetail": "{appName}, etiketi {clientName} uygulamasına ekleyemedi.", + "DownloadClientDownloadStationProviderMessage": "DSM hesabınızda 2 Faktörlü Kimlik Doğrulama etkinleştirilmişse {appName}, Download Station'a bağlanamaz", + "DownloadClientDownloadStationValidationApiVersion": "Download Station API sürümü desteklenmiyor; en az {requiredVersion} olmalıdır. {minVersion}'dan {maxVersion}'a kadar destekler", + "DownloadClientDownloadStationValidationNoDefaultDestination": "Varsayılan hedef yok", + "DownloadClientFloodSettingsAdditionalTagsHelpText": "Medyanın özelliklerini etiket olarak ekler. İpuçları örnektir.", + "DownloadClientFloodSettingsPostImportTagsHelpText": "İndirmelere içe aktarıldıktan sonra etiket ekler.", + "DownloadClientFloodSettingsUrlBaseHelpText": "Flood API'sine {url} gibi bir önek ekler", + "ReplaceIllegalCharactersHelpText": "Geçersiz karakterleri değiştirin. İşaretlenmezse bunun yerine {appName} bunları kaldıracak", + "ConnectionSettingsUrlBaseHelpText": "{connectionName} URL'sine {url} gibi bir önek ekler", + "DeleteSelectedImportLists": "İçe Aktarma Listelerini Sil", + "DelayingDownloadUntil": "İndirme işlemi {date} tarihine, {time} tarihine kadar erteleniyor", + "Destination": "Hedef", + "DoNotBlocklist": "Engelleme Listesine Eklemeyin", + "DoNotBlocklistHint": "Engellenenler listesine eklemeden kaldır", + "DownloadClientDelugeTorrentStateError": "Deluge bir hata bildiriyor", + "DownloadClientDelugeValidationLabelPluginFailure": "Etiket yapılandırılması başarısız oldu", + "DownloadClientDownloadStationValidationSharedFolderMissing": "Paylaşılan klasör mevcut değil", + "DeleteImportList": "İçe Aktarma Listesini Sil", + "IndexerPriorityHelpText": "Dizin Oluşturucu Önceliği (En Yüksek) 1'den (En Düşük) 50'ye kadar. Varsayılan: 25'dir. Eşit olmayan sürümler için eşitlik bozucu olarak sürümler alınırken kullanılan {appName}, RSS Senkronizasyonu ve Arama için etkinleştirilmiş tüm dizin oluşturucuları kullanmaya devam edecek", + "DisabledForLocalAddresses": "Yerel Adresler için Devre Dışı Bırakıldı", + "DownloadClientDelugeValidationLabelPluginInactive": "Etiket eklentisi etkinleştirilmedi", + "DownloadClientDelugeValidationLabelPluginInactiveDetail": "Kategorileri kullanmak için {clientName} uygulamasında Etiket eklentisini etkinleştirmiş olmanız gerekir.", + "DownloadClientDownloadStationValidationNoDefaultDestinationDetail": "Diskstation'ınızda {username} olarak oturum açmalı ve BT/HTTP/FTP/NZB -> Konum altında DownloadStation ayarlarında manuel olarak ayarlamalısınız.", + "DownloadClientDownloadStationValidationSharedFolderMissingDetail": "Diskstation'da '{sharedFolder}' adında bir Paylaşımlı Klasör yok, bunu doğru belirttiğinizden emin misiniz?", + "DownloadClientFloodSettingsRemovalInfo": "{appName}, Ayarlar -> Dizin Oluşturucular'daki mevcut tohum kriterlerine göre torrentlerin otomatik olarak kaldırılmasını gerçekleştirecek", + "Database": "Veri tabanı", + "DelayProfileProtocol": "Protokol: {preferredProtocol}", + "DownloadClientDownloadStationValidationFolderMissingDetail": "'{downloadDir}' klasörü mevcut değil, '{sharedFolder}' Paylaşımlı Klasöründe manuel olarak oluşturulması gerekiyor.", + "DeleteAutoTagHelpText": "'{name}' etiketini otomatik silmek istediğinizden emin misiniz?", + "DownloadClientDelugeSettingsUrlBaseHelpText": "Deluge json URL'sine bir önek ekler, bkz. {url}", + "DownloadClientFreeboxSettingsPortHelpText": "Freebox arayüzüne erişim için kullanılan bağlantı noktası, varsayılan olarak '{port}' şeklindedir", + "DownloadClientFreeboxUnableToReachFreebox": "Freebox API'sine ulaşılamıyor. 'Ana Bilgisayar', 'Bağlantı Noktası' veya 'SSL Kullan' ayarlarını doğrulayın. (Hata: {istisnaMessage})", + "CustomFormatsSettingsTriggerInfo": "Bir yayına veya dosyaya, seçilen farklı koşul türlerinden en az biriyle eşleştiğinde Özel Format uygulanacaktır.", + "Default": "Varsayılan", + "DeleteSelectedImportListsMessageText": "Seçilen {count} içe aktarma listesini silmek istediğinizden emin misiniz?", + "DownloadClientDelugeSettingsDirectory": "İndirme Dizini", + "DownloadClientDelugeSettingsDirectoryCompleted": "Tamamlandığında Dizini Taşı", + "DownloadClientDelugeSettingsDirectoryCompletedHelpText": "Tamamlanan indirmelerin taşınacağı isteğe bağlı konum; varsayılan Deluge konumunu kullanmak için boş bırakın", + "DownloadClientDelugeSettingsDirectoryHelpText": "İndirilenlerin yerleştirileceği isteğe bağlı konum; varsayılan Deluge konumunu kullanmak için boş bırakın", + "DownloadClientDownloadStationSettingsDirectoryHelpText": "İndirilenlerin yerleştirileceği isteğe bağlı paylaşımlı klasör, varsayılan Download Station konumunu kullanmak için boş bırakın", + "ApiKey": "API Anahtarı", + "Analytics": "Analiz", + "All": "Herşey", + "AppDataLocationHealthCheckMessage": "Güncellemede AppData'nın silinmesini önlemek için güncelleme mümkün olmayacak", + "AnalyticsEnabledHelpText": "Anonim kullanım ve hata bilgilerini {appName} sunucularına gönderin. Bu, tarayıcınızla ilgili bilgileri, kullandığınız {appName} Web arayüz sayfalarını, hata raporlamasının yanı sıra işletim sistemi ve çalışma zamanı sürümünü içerir. Bu bilgileri, özellikleri ve hata düzeltmelerini önceliklendirmek için kullanacağız." } diff --git a/src/NzbDrone.Core/Parser/LanguageParser.cs b/src/NzbDrone.Core/Parser/LanguageParser.cs index 4071539c0..1548e4f82 100644 --- a/src/NzbDrone.Core/Parser/LanguageParser.cs +++ b/src/NzbDrone.Core/Parser/LanguageParser.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Parser new RegexReplace(@".*?[_. ](S\d{2}(?:E\d{2,4})*[_. ].*)", "$1", RegexOptions.Compiled | RegexOptions.IgnoreCase) }; - private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?\b(?:ing|eng)\b)|(?\b(?:ita|italian)\b)|(?german\b|videomann|ger[. ]dub)|(?flemish)|(?greek)|(?(?:\W|_)(?:FR|VF|VF2|VFF|VFQ|TRUEFRENCH)(?:\W|_))|(?\b(?:rus|ru)\b)|(?\b(?:HUNDUB|HUN)\b)|(?\bHebDub\b)|(?\b(?:PL\W?DUB|DUB\W?PL|LEK\W?PL|PL\W?LEK)\b)|(?\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕)|(?\bbgaudio\b)|(?\b(?:español|castellano|esp|spa(?!\(Latino\)))\b)|(?\b(?:ukr)\b)|(?\b(?:THAI)\b)|(?\b(?:RoDubbed|ROMANIAN)\b)|(?[-,. ]cat[. ](?:DD|subs)|\b(?:catalan|catalán)\b)|(?\b(?:lat|lav|lv)\b)", + private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?\b(?:ing|eng)\b)|(?\b(?:ita|italian)\b)|(?german\b|videomann|ger[. ]dub)|(?flemish)|(?greek)|(?(?:\W|_)(?:FR|VF|VF2|VFF|VFQ|TRUEFRENCH)(?:\W|_))|(?\b(?:rus|ru)\b)|(?\b(?:HUNDUB|HUN)\b)|(?\bHebDub\b)|(?\b(?:PL\W?DUB|DUB\W?PL|LEK\W?PL|PL\W?LEK)\b)|(?\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕)|(?\bbgaudio\b)|(?\b(?:español|castellano|esp|spa(?!\(Latino\)))\b)|(?\b(?:\dx?)?(?:ukr))|(?\b(?:THAI)\b)|(?\b(?:RoDubbed|ROMANIAN)\b)|(?[-,. ]cat[. ](?:DD|subs)|\b(?:catalan|catalán)\b)|(?\b(?:lat|lav|lv)\b)|(?\b(?:tur)\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex CaseSensitiveLanguageRegex = new Regex(@"(?:(?i)(?\bLT\b)|(?\bCZ\b)|(?\bPL\b)|(?\bBG\b)|(?\bSK\b))(?:(?i)(?![\W|_|^]SUB))", @@ -470,6 +470,11 @@ namespace NzbDrone.Core.Parser { languages.Add(Language.Latvian); } + + if (match.Groups["turkish"].Success) + { + languages.Add(Language.Turkish); + } } return languages; diff --git a/src/NzbDrone.Core/Sonarr.Core.csproj b/src/NzbDrone.Core/Sonarr.Core.csproj index e1e32454e..c693aaf45 100644 --- a/src/NzbDrone.Core/Sonarr.Core.csproj +++ b/src/NzbDrone.Core/Sonarr.Core.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Sonarr.Api.V3/ProviderControllerBase.cs b/src/Sonarr.Api.V3/ProviderControllerBase.cs index ca1082609..2622b9b02 100644 --- a/src/Sonarr.Api.V3/ProviderControllerBase.cs +++ b/src/Sonarr.Api.V3/ProviderControllerBase.cs @@ -205,10 +205,10 @@ namespace Sonarr.Api.V3 [SkipValidation(true, false)] [HttpPost("test")] [Consumes("application/json")] - public object Test([FromBody] TProviderResource providerResource) + public object Test([FromBody] TProviderResource providerResource, [FromQuery] bool forceTest = false) { var existingDefinition = providerResource.Id > 0 ? _providerFactory.Find(providerResource.Id) : null; - var providerDefinition = GetDefinition(providerResource, existingDefinition, true, true, true); + var providerDefinition = GetDefinition(providerResource, existingDefinition, true, !forceTest, true); Test(providerDefinition, true); diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index 14444bdba..4492f1d6a 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -1654,6 +1654,16 @@ "tags": [ "DownloadClient" ], + "parameters": [ + { + "name": "forceTest", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], "requestBody": { "content": { "application/json": { @@ -2897,6 +2907,16 @@ "tags": [ "ImportList" ], + "parameters": [ + { + "name": "forceTest", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], "requestBody": { "content": { "application/json": { @@ -3487,6 +3507,16 @@ "tags": [ "Indexer" ], + "parameters": [ + { + "name": "forceTest", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], "requestBody": { "content": { "application/json": { @@ -4470,6 +4500,16 @@ "tags": [ "Metadata" ], + "parameters": [ + { + "name": "forceTest", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], "requestBody": { "content": { "application/json": { @@ -5032,6 +5072,16 @@ "tags": [ "Notification" ], + "parameters": [ + { + "name": "forceTest", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], "requestBody": { "content": { "application/json": {