mirror of
https://github.com/Jackett/Jackett
synced 2025-03-06 19:58:38 +00:00
243 lines
8.6 KiB
C#
243 lines
8.6 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using Jackett.Common.Extensions;
|
|
using Jackett.Common.Utils;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace Jackett.Common.Models
|
|
{
|
|
public class TorznabQuery
|
|
{
|
|
private static readonly Regex _StandardizeDashesRegex = new Regex(@"\p{Pd}+", RegexOptions.Compiled);
|
|
private static readonly Regex _StandardizeSingleQuotesRegex = new Regex(@"[\u0060\u00B4\u2018\u2019]", RegexOptions.Compiled);
|
|
|
|
public string QueryType { get; set; }
|
|
public int[] Categories { get; set; }
|
|
public int Extended { get; set; }
|
|
public string ApiKey { get; set; }
|
|
public int Limit { get; set; }
|
|
public int Offset { get; set; }
|
|
public int? RageID { get; set; }
|
|
public int? TvdbID { get; set; }
|
|
public string ImdbID { get; set; }
|
|
public int? TmdbID { get; set; }
|
|
public int? TvmazeID { get; set; }
|
|
public int? TraktID { get; set; }
|
|
public int? DoubanID { get; set; }
|
|
|
|
[JsonIgnore]
|
|
public bool Cache { get; set; } = true;
|
|
|
|
public int Season { get; set; }
|
|
public string Episode { get; set; }
|
|
public string SearchTerm { get; set; }
|
|
|
|
public string Album { get; set; }
|
|
public string Artist { get; set; }
|
|
public string Label { get; set; }
|
|
public string Track { get; set; }
|
|
public int? Year { get; set; }
|
|
public string Genre { get; set; }
|
|
|
|
public string Author { get; set; }
|
|
public string Title { get; set; }
|
|
public string Publisher { get; set; }
|
|
|
|
public bool IsTest { get; set; }
|
|
|
|
public string ImdbIDShort => ImdbID?.TrimStart('t');
|
|
|
|
protected string[] QueryStringParts;
|
|
|
|
public bool IsSearch => QueryType == "search";
|
|
|
|
public bool IsTVSearch => QueryType == "tvsearch";
|
|
|
|
public bool IsMovieSearch => QueryType == "movie" || (QueryType == "TorrentPotato" && !string.IsNullOrWhiteSpace(SearchTerm));
|
|
|
|
public bool IsMusicSearch => QueryType == "music";
|
|
|
|
public bool IsBookSearch => QueryType == "book";
|
|
|
|
public bool IsTVRageSearch => RageID != null;
|
|
|
|
public bool IsTvdbSearch => TvdbID != null;
|
|
|
|
public bool IsImdbQuery => ImdbID != null;
|
|
|
|
public bool IsTmdbQuery => TmdbID != null;
|
|
|
|
public bool IsTvmazeQuery => TvmazeID != null;
|
|
|
|
public bool IsTraktQuery => TraktID != null;
|
|
|
|
public bool IsDoubanQuery => DoubanID != null;
|
|
|
|
public bool IsGenreQuery => Genre != null;
|
|
|
|
public bool HasSpecifiedCategories => (Categories != null && Categories.Length > 0);
|
|
|
|
public string SanitizedSearchTerm
|
|
{
|
|
get
|
|
{
|
|
var term = SearchTerm ?? "";
|
|
|
|
term = _StandardizeDashesRegex.Replace(term, "-");
|
|
term = _StandardizeSingleQuotesRegex.Replace(term, "'");
|
|
|
|
var safeTitle = term.Where(c => (char.IsLetterOrDigit(c)
|
|
|| char.IsWhiteSpace(c)
|
|
|| c == '-'
|
|
|| c == '.'
|
|
|| c == '_'
|
|
|| c == '('
|
|
|| c == ')'
|
|
|| c == '@'
|
|
|| c == '/'
|
|
|| c == '\''
|
|
|| c == '['
|
|
|| c == ']'
|
|
|| c == '+'
|
|
|| c == '%'
|
|
|| c == ':'
|
|
));
|
|
|
|
return string.Concat(safeTitle);
|
|
}
|
|
}
|
|
|
|
public TorznabQuery()
|
|
{
|
|
Categories = Array.Empty<int>();
|
|
IsTest = false;
|
|
}
|
|
|
|
public TorznabQuery CreateFallback(string search)
|
|
{
|
|
var ret = Clone();
|
|
if (Categories == null || Categories.Length == 0)
|
|
{
|
|
ret.Categories = new[]
|
|
{
|
|
TorznabCatType.Movies.ID,
|
|
TorznabCatType.MoviesForeign.ID,
|
|
TorznabCatType.MoviesOther.ID,
|
|
TorznabCatType.MoviesSD.ID,
|
|
TorznabCatType.MoviesHD.ID,
|
|
TorznabCatType.Movies3D.ID,
|
|
TorznabCatType.MoviesBluRay.ID,
|
|
TorznabCatType.MoviesDVD.ID,
|
|
TorznabCatType.MoviesWEBDL.ID,
|
|
TorznabCatType.MoviesUHD.ID,
|
|
};
|
|
}
|
|
ret.SearchTerm = search;
|
|
|
|
return ret;
|
|
}
|
|
|
|
public TorznabQuery Clone()
|
|
{
|
|
var ret = new TorznabQuery
|
|
{
|
|
QueryType = QueryType,
|
|
Extended = Extended,
|
|
ApiKey = ApiKey,
|
|
Limit = Limit,
|
|
Offset = Offset,
|
|
Season = Season,
|
|
Episode = Episode,
|
|
SearchTerm = SearchTerm,
|
|
IsTest = IsTest,
|
|
Album = Album,
|
|
Artist = Artist,
|
|
Label = Label,
|
|
Track = Track,
|
|
Genre = Genre,
|
|
Year = Year,
|
|
Author = Author,
|
|
Title = Title,
|
|
Publisher = Publisher,
|
|
RageID = RageID,
|
|
TvdbID = TvdbID,
|
|
ImdbID = ImdbID,
|
|
TmdbID = TmdbID,
|
|
TvmazeID = TvmazeID,
|
|
TraktID = TraktID,
|
|
DoubanID = DoubanID,
|
|
Cache = Cache
|
|
};
|
|
if (Categories?.Length > 0)
|
|
{
|
|
ret.Categories = new int[Categories.Length];
|
|
Array.Copy(Categories, ret.Categories, Categories.Length);
|
|
}
|
|
if (QueryStringParts?.Length > 0)
|
|
{
|
|
ret.QueryStringParts = new string[QueryStringParts.Length];
|
|
Array.Copy(QueryStringParts, ret.QueryStringParts, QueryStringParts.Length);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public string GetQueryString() => (SanitizedSearchTerm + " " + GetEpisodeSearchString()).Trim();
|
|
|
|
// Some trackers don't support AND logic for search terms resulting in unwanted results.
|
|
// Using this method we can AND filter it within jackett.
|
|
// With limit we can limit the amount of characters which should be compared (use it if a tracker doesn't return the full title).
|
|
public bool MatchQueryStringAND(string title, int? limit = null, string queryStringOverride = null)
|
|
{
|
|
var commonWords = new[] { "and", "the", "an" };
|
|
|
|
// We cache the regex split results so we have to do it only once for each query.
|
|
if (QueryStringParts == null)
|
|
{
|
|
var queryString = !string.IsNullOrWhiteSpace(queryStringOverride) ? queryStringOverride : GetQueryString();
|
|
|
|
if (limit != null && limit > 0)
|
|
{
|
|
if (limit > queryString.Length)
|
|
limit = queryString.Length;
|
|
|
|
queryString = queryString.Substring(0, (int)limit);
|
|
}
|
|
|
|
var splitRegex = new Regex("[^\\w]+");
|
|
|
|
QueryStringParts = splitRegex.Split(queryString).Where(p => !string.IsNullOrWhiteSpace(p) && p.Length > 1 && !commonWords.ContainsIgnoreCase(p)).ToArray();
|
|
}
|
|
|
|
// Check if each part of the query string is in the given title.
|
|
return QueryStringParts.All(title.ContainsIgnoreCase);
|
|
}
|
|
|
|
public string GetEpisodeSearchString()
|
|
{
|
|
if (Season == 0)
|
|
return string.Empty;
|
|
|
|
string episodeString;
|
|
if (DateTime.TryParseExact($"{Season} {Episode}", "yyyy MM/dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var showDate))
|
|
episodeString = showDate.ToString("yyyy.MM.dd");
|
|
else if (string.IsNullOrEmpty(Episode))
|
|
episodeString = $"S{Season:00}";
|
|
else
|
|
{
|
|
try
|
|
{
|
|
episodeString = $"S{Season:00}E{ParseUtil.CoerceInt(Episode):00}";
|
|
}
|
|
catch (FormatException) // e.g. seaching for S01E01A
|
|
{
|
|
episodeString = $"S{Season:00}E{Episode}";
|
|
}
|
|
|
|
}
|
|
return episodeString;
|
|
}
|
|
}
|
|
}
|