diff --git a/src/NzbDrone.Core.Test/ParserTests/SlugParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SlugParserFixture.cs new file mode 100644 index 000000000..a50d8e34f --- /dev/null +++ b/src/NzbDrone.Core.Test/ParserTests/SlugParserFixture.cs @@ -0,0 +1,161 @@ +using FluentAssertions; + +using NUnit.Framework; + +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.ParserTests +{ + [TestFixture] + public class SlugParserFixture : CoreTest + { + [TestCase("tèst", "test")] + [TestCase("têst", "test")] + [TestCase("tëst", "test")] + [TestCase("tËst", "test")] + [TestCase("áccent", "accent")] + [TestCase("àccent", "accent")] + [TestCase("âccent", "accent")] + [TestCase("Äccent", "accent")] + [TestCase("åccent", "accent")] + [TestCase("acceñt", "accent")] + [TestCase("ßtest", "test")] + [TestCase("œtest", "test")] + [TestCase("Œtest", "test")] + [TestCase("Øtest", "test")] + public void should_replace_accents(string input, string result) + { + Parser.Parser.ToUrlSlug(input).Should().Be(result); + } + + [TestCase("Test'Result")] + [TestCase("Test$Result")] + [TestCase("Test(Result")] + [TestCase("Test)Result")] + [TestCase("Test*Result")] + [TestCase("Test?Result")] + [TestCase("Test/Result")] + [TestCase("Test=Result")] + [TestCase("Test\\Result")] + public void should_replace_special_characters(string input) + { + Parser.Parser.ToUrlSlug(input).Should().Be("testresult"); + } + + [TestCase("ThIS IS A MiXeD CaSe SensItIvE ValUe")] + public void should_lowercase_capitals(string input) + { + Parser.Parser.ToUrlSlug(input).Should().Be("this-is-a-mixed-case-sensitive-value"); + } + + [TestCase("test----")] + [TestCase("test____")] + [TestCase("test-_--_")] + public void should_trim_trailing_dashes_and_underscores(string input) + { + Parser.Parser.ToUrlSlug(input).Should().Be("test"); + } + + [TestCase("test result")] + [TestCase("test result")] + public void should_replace_spaces_with_dash(string input) + { + Parser.Parser.ToUrlSlug(input).Should().Be("test-result"); + } + + [TestCase("test result", "test-result")] + [TestCase("test-----result", "test-result")] + [TestCase("test_____result", "test_result")] + public void should_replace_double_occurence(string input, string result) + { + Parser.Parser.ToUrlSlug(input).Should().Be(result); + } + + [TestCase("Test'Result")] + [TestCase("Test$Result")] + [TestCase("Test(Result")] + [TestCase("Test)Result")] + [TestCase("Test*Result")] + [TestCase("Test?Result")] + [TestCase("Test/Result")] + [TestCase("Test=Result")] + [TestCase("Test\\Result")] + public void should_replace_special_characters_with_dash_when_enabled(string input) + { + Parser.Parser.ToUrlSlug(input, true).Should().Be("test-result"); + } + + [TestCase("Test'Result")] + [TestCase("Test$Result")] + [TestCase("Test(Result")] + [TestCase("Test)Result")] + [TestCase("Test*Result")] + [TestCase("Test?Result")] + [TestCase("Test/Result")] + [TestCase("Test=Result")] + [TestCase("Test\\Result")] + public void should__not_replace_special_characters_with_dash_when_disabled(string input) + { + Parser.Parser.ToUrlSlug(input, false).Should().Be("testresult"); + } + + [TestCase("test----", "-_", "test")] + [TestCase("test____", "-_", "test")] + [TestCase("test-_-_", "-_", "test")] + [TestCase("test----", "-", "test")] + [TestCase("test____", "-", "test____")] + [TestCase("test-_-_", "-", "test-_-_")] + [TestCase("test----", "_", "test----")] + [TestCase("test____", "_", "test")] + [TestCase("test-_-_", "_", "test-_-")] + [TestCase("test----", "", "test----")] + [TestCase("test____", "", "test____")] + [TestCase("test-_-_", "", "test-_-_")] + public void should_trim_trailing_dashes_and_underscores_based_on_list(string input, string trimList, string result) + { + Parser.Parser.ToUrlSlug(input, false, trimList, "").Should().Be(result); + } + + [TestCase("test----result", "-_", "test-result")] + [TestCase("test____result", "-_", "test_result")] + [TestCase("test_-_-result", "-_", "test-result")] + [TestCase("test-_-_result", "-_", "test_result")] + [TestCase("test----result", "-", "test-result")] + [TestCase("test____result", "-", "test____result")] + [TestCase("test-_-_result", "-", "test-_-_result")] + [TestCase("test----result", "_", "test----result")] + [TestCase("test____result", "_", "test_result")] + [TestCase("test-_-_result", "_", "test-_-_result")] + [TestCase("test----result", "", "test----result")] + [TestCase("test____result", "", "test____result")] + [TestCase("test-_-_result", "", "test-_-_result")] + public void should_replace_duplicate_characters_based_on_list(string input, string deduplicateChars, string result) + { + Parser.Parser.ToUrlSlug(input, false, "", deduplicateChars).Should().Be(result); + } + + [Test] + public void should_handle_null_trim_parameters() + { + Parser.Parser.ToUrlSlug("test", false, null, "-_").Should().Be("test"); + } + + [Test] + public void should_handle_null_dedupe_parameters() + { + Parser.Parser.ToUrlSlug("test", false, "-_", null).Should().Be("test"); + } + + [Test] + public void should_handle_empty_trim_parameters() + { + Parser.Parser.ToUrlSlug("test", false, "", "-_").Should().Be("test"); + } + + [Test] + public void should_handle_empty_dedupe_parameters() + { + Parser.Parser.ToUrlSlug("test", false, "-_", "").Should().Be("test"); + } + } +} diff --git a/src/NzbDrone.Core/ImportLists/Trakt/List/TraktListRequestGenerator.cs b/src/NzbDrone.Core/ImportLists/Trakt/List/TraktListRequestGenerator.cs index 78b4b6885..42ee40d08 100644 --- a/src/NzbDrone.Core/ImportLists/Trakt/List/TraktListRequestGenerator.cs +++ b/src/NzbDrone.Core/ImportLists/Trakt/List/TraktListRequestGenerator.cs @@ -27,7 +27,13 @@ private IEnumerable GetMoviesRequest() { var link = string.Empty; - var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim()); + // Trakt slug rules: + // - replace all special characters with a dash + // - replaces multiple dashes with a single dash + // - allows underscore as a valid character + // - does not trim underscore from the end + // - allows multiple underscores in a row + var listName = Parser.Parser.ToUrlSlug(Settings.Listname.Trim(), true, "-", "-"); link += $"users/{Settings.Username.Trim()}/lists/{listName}/items/movies?limit={Settings.Limit}"; var request = new ImportListRequest(_traktProxy.BuildTraktRequest(link, HttpMethod.Get, Settings.AccessToken)); diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 63260825a..5fab19141 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -401,7 +401,7 @@ public static string NormalizeImdbId(string imdbId) return null; } - public static string ToUrlSlug(string value) + public static string ToUrlSlug(string value, bool invalidDashReplacement = false, string trimEndChars = "-_", string deduplicateChars = "-_") { //First to lower case value = value.ToLowerInvariant(); @@ -412,14 +412,23 @@ public static string ToUrlSlug(string value) //Replace spaces value = Regex.Replace(value, @"\s", "-", RegexOptions.Compiled); + //Should invalid characters be replaced with dash or empty string? + string replaceCharacter = invalidDashReplacement ? "-" : string.Empty; + //Remove invalid chars - value = Regex.Replace(value, @"[^a-z0-9\s-_]", "", RegexOptions.Compiled); + value = Regex.Replace(value, @"[^a-z0-9\s-_]", replaceCharacter, RegexOptions.Compiled); - //Trim dashes from end - value = value.Trim('-', '_'); + //Trim dashes or underscores from end, or user defined character set + if (!string.IsNullOrEmpty(trimEndChars)) + { + value = value.Trim(trimEndChars.ToCharArray()); + } - //Replace double occurences of - or _ - value = Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled); + //Replace double occurrences of - or _, or user defined character set + if (!string.IsNullOrEmpty(deduplicateChars)) + { + value = Regex.Replace(value, @"([" + deduplicateChars + "]){2,}", "$1", RegexOptions.Compiled); + } return value; }