diff --git a/src/Jackett/Indexers/T411.cs b/src/Jackett/Indexers/T411.cs new file mode 100644 index 0000000000000000000000000000000000000000..cbaff7445d33a1430e52b51026575b4a737226c6 --- /dev/null +++ b/src/Jackett/Indexers/T411.cs @@ -0,0 +1,200 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace Jackett.Indexers +{ + public class T411 : IndexerInterface + { + + public event Action<IndexerInterface, JToken> OnSaveConfigurationRequested; + + public event Action<IndexerInterface, string, Exception> OnResultParsingError; + + public string DisplayName + { + get { return "T411"; } + } + + public string DisplayDescription + { + get { return "French Torrent Tracker"; } + } + + public Uri SiteLink + { + get { return new Uri(BaseUrl); } + } + + public bool IsConfigured { get; private set; } + + const string BaseUrl = "http://www.t411.io"; + const string CommentsUrl = BaseUrl + "/torrents/{0}"; + + const string ApiUrl = "http://api.t411.io"; + const string AuthUrl = ApiUrl + "/auth"; + const string SearchUrl = ApiUrl + "/torrents/search/{0}"; + const string DownloadUrl = ApiUrl + "/torrents/download/{0}"; + + HttpClientHandler handler; + HttpClient client; + + string username = string.Empty; + string password = string.Empty; + string token = string.Empty; + DateTime lastTokenFetch = DateTime.MinValue; + + public T411() + { + IsConfigured = false; + handler = new HttpClientHandler + { + AllowAutoRedirect = true + }; + client = new HttpClient(handler); + } + + public Task<ConfigurationData> GetConfigurationForSetup() + { + var config = new ConfigurationDataBasicLogin(); + return Task.FromResult<ConfigurationData>(config); + } + + async Task<string> GetAuthToken(bool forceFetch = false) + { + if (!forceFetch && lastTokenFetch > DateTime.Now - TimeSpan.FromHours(48)) + { + return token; + } + + var pairs = new Dictionary<string, string> { + { "username", username }, + { "password", password } + }; + + var content = new FormUrlEncodedContent(pairs); + + var response = await client.PostAsync(AuthUrl, content); + var responseContent = await response.Content.ReadAsStringAsync(); + var jsonResponse = JObject.Parse(responseContent); + if (jsonResponse["error"] != null) + { + throw new ApplicationException((string)jsonResponse["error"]); + } + token = (string)jsonResponse["token"]; + lastTokenFetch = DateTime.Now; + return token; + } + + public async Task ApplyConfiguration(JToken configJson) + { + var config = new ConfigurationDataBasicLogin(); + config.LoadValuesFromJson(configJson); + + username = config.Username.Value; + password = config.Password.Value; + + try + { + await GetAuthToken(true); + } + catch (Exception ex) + { + throw new ExceptionWithConfigData(ex.Message, (ConfigurationData)config); + } + + var configSaveData = new JObject(); + configSaveData["username"] = username; + configSaveData["password"] = password; + configSaveData["token"] = token; + configSaveData["last_token_fetch"] = lastTokenFetch; + + if (OnSaveConfigurationRequested != null) + OnSaveConfigurationRequested(this, configSaveData); + + IsConfigured = true; + } + + public void LoadFromSavedConfiguration(JToken jsonConfig) + { + username = (string)jsonConfig["username"]; + password = (string)jsonConfig["password"]; + token = (string)jsonConfig["token"]; + lastTokenFetch = (DateTime)jsonConfig["last_token_fetch"]; + IsConfigured = true; + } + + public async Task<ReleaseInfo[]> PerformQuery(TorznabQuery query) + { + List<ReleaseInfo> releases = new List<ReleaseInfo>(); + + foreach (var title in query.ShowTitles ?? new string[] { "%20" }) + { + var searchString = title + " " + query.GetEpisodeSearchString(); + var episodeSearchUrl = string.Format(SearchUrl, HttpUtility.UrlEncode(searchString)); + + var message = new HttpRequestMessage(); + message.Method = HttpMethod.Get; + message.RequestUri = new Uri(episodeSearchUrl); + message.Headers.TryAddWithoutValidation("Authorization", token); + + var response = await client.SendAsync(message); + var results = await response.Content.ReadAsStringAsync(); + + var jsonResult = JObject.Parse(results); + try + { + var items = (JArray)jsonResult["torrents"]; + foreach (var item in items) + { + var release = new ReleaseInfo(); + + release.MinimumRatio = 1; + release.MinimumSeedTime = 172800; + var torrentId = (string)item["id"]; + release.Link = new Uri(string.Format(DownloadUrl, torrentId)); + release.Title = (string)item["name"]; + release.Description = release.Title; + release.Comments = new Uri(string.Format(CommentsUrl, (string)item["rewritename"])); + release.Guid = release.Comments; + + var dateUtc = DateTime.ParseExact((string)item["added"], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); + release.PublishDate = DateTime.SpecifyKind(dateUtc, DateTimeKind.Utc).ToLocalTime(); + + release.Seeders = ParseUtil.CoerceInt((string)item["seeders"]); + release.Peers = ParseUtil.CoerceInt((string)item["leechers"]) + release.Seeders; + + release.Size = ParseUtil.CoerceLong((string)item["size"]); + + releases.Add(release); + } + } + catch (Exception ex) + { + OnResultParsingError(this, results, ex); + throw ex; + } + } + return releases.ToArray(); + } + + public async Task<byte[]> Download(Uri link) + { + var message = new HttpRequestMessage(); + message.Method = HttpMethod.Get; + message.RequestUri = link; + message.Headers.TryAddWithoutValidation("Authorization", token); + + var response = await client.SendAsync(message); + return await response.Content.ReadAsByteArrayAsync(); + } + } +} diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index 532f6f42f76e91de5e66f309147cca1d9fcc0974..e53951a7257e6238217875508d13da2d15ab5a25 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -107,6 +107,7 @@ <Compile Include="Indexers\SceneTime.cs" /> <Compile Include="Indexers\ShowRSS.cs" /> <Compile Include="Indexers\Strike.cs" /> + <Compile Include="Indexers\T411.cs" /> <Compile Include="Indexers\ThePirateBay.cs" /> <Compile Include="Indexers\TorrentDay.cs" /> <Compile Include="Indexers\AnimeBytes.cs" /> @@ -181,6 +182,9 @@ <Content Include="WebContent\logos\showrss.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="WebContent\logos\t411.png"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="WebContent\logos\torrentday.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> diff --git a/src/Jackett/WebContent/logos/t411.png b/src/Jackett/WebContent/logos/t411.png new file mode 100644 index 0000000000000000000000000000000000000000..30e13134a219fcb3892657209c8f356aad8744f0 Binary files /dev/null and b/src/Jackett/WebContent/logos/t411.png differ