using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using AngleSharp.Html.Parser; using Jackett.Common.Models; using Jackett.Common.Models.IndexerConfig; using Jackett.Common.Services.Interfaces; using Jackett.Common.Utils; using Jackett.Common.Utils.Clients; using Newtonsoft.Json.Linq; using NLog; namespace Jackett.Common.Indexers { [ExcludeFromCodeCoverage] public class Libble : BaseWebIndexer { private string LandingUrl => SiteLink + "login.php"; private string LoginUrl => SiteLink + "login.php"; private string SearchUrl => SiteLink + "torrents.php"; private Dictionary CategoryMappings = new Dictionary{ { "cats_music", "Music" }, { "cats_libblemixtapes", "Libble Mixtapes" }, { "cats_musicvideos", "Music Videos" } }; class VolumeFactorTag { public double DownloadVolumeFactor { get; set; } = 1.0; public double UploadVolumeFactor { get; set; } = 1.0; } private Dictionary VolumeTagMappings = new Dictionary{ { "Neutral!", new VolumeFactorTag { DownloadVolumeFactor = 0, UploadVolumeFactor = 0 } }, { "Freeleech!", new VolumeFactorTag { DownloadVolumeFactor = 0, UploadVolumeFactor = 1 } } }; private new ConfigurationDataBasicLogin configData { get => (ConfigurationDataBasicLogin)base.configData; set => base.configData = value; } public Libble(IIndexerConfigurationService configService, WebClient wc, Logger l, IProtectionService ps, ICacheService cs) : base(id: "libble", name: "Libble", description: "Libble is a Private Torrent Tracker for MUSIC", link: "https://libble.me/", caps: new TorznabCapabilities { MusicSearchParams = new List { MusicSearchParam.Q, MusicSearchParam.Album, MusicSearchParam.Artist, MusicSearchParam.Label, MusicSearchParam.Year } }, configService: configService, client: wc, logger: l, p: ps, cacheService: cs, configData: new ConfigurationDataBasicLogin()) { Encoding = Encoding.UTF8; Language = "en-us"; Type = "private"; AddCategoryMapping(1, TorznabCatType.Audio, "Music"); AddCategoryMapping(2, TorznabCatType.Audio, "Libble Mixtapes"); AddCategoryMapping(7, TorznabCatType.AudioVideo, "Music Videos"); } public override async Task GetConfigurationForSetup() { await RequestWithCookiesAsync(LandingUrl); return configData; } public override async Task ApplyConfiguration(JToken configJson) { LoadValuesFromJson(configJson); var pairs = new Dictionary { {"username", configData.Username.Value}, {"password", configData.Password.Value}, {"login", "Login"} }; var result = await RequestLoginAndFollowRedirect(LoginUrl, pairs, null, true, SearchUrl, LandingUrl, true); await ConfigureIfOK(result.Cookies, result.ContentString?.Contains("logout.php") == true, () => { var parser = new HtmlParser(); var dom = parser.ParseDocument(result.ContentString); var warningNode = dom.QuerySelector("#loginform > .warning"); var errorMessage = warningNode?.TextContent.Trim().Replace("\n\t", " "); throw new ExceptionWithConfigData(errorMessage, configData); }); SaveConfig(); return IndexerConfigurationStatus.RequiresTesting; } protected override async Task> PerformQuery(TorznabQuery query) { // https://libble.me/torrents.php?searchstr=the+used&taglist=&tags_type=1&order_by=time&order_way=desc var releases = new List(); var searchString = query.GetQueryString(); var searchUrl = SearchUrl; var searchParams = new Dictionary { }; var queryCollection = new NameValueCollection { }; // Search String if (!string.IsNullOrWhiteSpace(query.ImdbID)) queryCollection.Add("cataloguenumber", query.ImdbID); else if (!string.IsNullOrWhiteSpace(searchString)) queryCollection.Add("searchstr", searchString); // Filter Categories if (query.HasSpecifiedCategories) { foreach (var cat in MapTorznabCapsToTrackers(query)) { queryCollection.Add("filter_cat[" + cat.ToString() + "]", "1"); } } if (query.Artist != null) queryCollection.Add("artistname", query.Artist); if (query.Label != null) queryCollection.Add("recordlabel", query.Label); if (query.Year != null) queryCollection.Add("year", query.Year.ToString()); if (query.Album != null) queryCollection.Add("groupname", query.Album); searchUrl += "?" + queryCollection.GetQueryString(); var searchPage = await RequestWithCookiesAndRetryAsync(searchUrl, method: RequestType.POST, data: searchParams); // Occasionally the cookies become invalid, login again if that happens if (searchPage.IsRedirect) { await ApplyConfiguration(null); searchPage = await RequestWithCookiesAndRetryAsync(searchUrl, method: RequestType.POST, data: searchParams); } try { var parser = new HtmlParser(); var dom = parser.ParseDocument(searchPage.ContentString); var albumRows = dom.QuerySelectorAll("table#torrent_table > tbody > tr:has(strong > a[href*=\"torrents.php?id=\"])"); foreach (var row in albumRows) { var releaseGroupRegex = new Regex(@"torrents\.php\?id=([0-9]+)"); var albumNameNode = row.QuerySelector("strong > a[href*=\"torrents.php?id=\"]"); var artistsNameNodes = row.QuerySelectorAll("strong > a[href*=\"artist.php?id=\"]"); var albumYearNode = albumNameNode.NextSibling; var categoryNode = row.QuerySelector(".cats_col > div"); var thumbnailNode = row.QuerySelector(".thumbnail"); var releaseArtist = "Various Artists"; if (artistsNameNodes.Count() > 0) { var aristNames = new List(); foreach (var aristNode in artistsNameNodes) { aristNames.Add(aristNode.TextContent.Trim()); } releaseArtist = string.Join(", ", aristNames); } var releaseAlbumName = albumNameNode.TextContent.Trim(); var releaseGroupId = ParseUtil.CoerceInt(releaseGroupRegex.Match(albumNameNode.GetAttribute("href")).Groups[1].ToString()); var releaseAlbumYear = ParseUtil.CoerceInt(albumYearNode.TextContent.Replace("[", "").Replace("]", "").Trim()); Uri releaseThumbnailUri = null; if (thumbnailNode != null) releaseThumbnailUri = new Uri(thumbnailNode.GetAttribute("title")); ICollection releaseNewznabCategory = null; var categoriesSplit = categoryNode.ClassName.Split(' '); foreach (var rawCategory in categoriesSplit) { if (CategoryMappings.ContainsKey(rawCategory)) { var newznabCat = MapTrackerCatDescToNewznab(CategoryMappings[rawCategory]); if (newznabCat.Count != 0) releaseNewznabCategory = newznabCat; } } var releaseRows = dom.QuerySelectorAll(String.Format(".group_torrent.groupid_{0}", releaseGroupId)); string lastEdition = null; foreach (var releaseDetails in releaseRows) { var editionInfoDetails = releaseDetails.QuerySelector(".edition_info"); // Process as release details if (editionInfoDetails != null) { lastEdition = editionInfoDetails.QuerySelector("strong").TextContent; } // Process as torrent else { // https://libble.me/torrents.php?id=51694&torrentid=89758 var release = new ReleaseInfo(); var releaseMediaDetails = releaseDetails.Children[0].Children[1]; var releaseFileCountDetails = releaseDetails.Children[1]; var releaseDateDetails = releaseDetails.Children[2].Children[0]; var releaseSizeDetails = releaseDetails.Children[3]; var releaseGrabsDetails = releaseDetails.Children[4]; var releaseSeedsCountDetails = releaseDetails.Children[5]; var releasePeersCountDetails = releaseDetails.Children[6]; var releaseDownloadDetails = releaseDetails.QuerySelector("a[href*=\"action=download\"]"); var releaseMediaType = releaseMediaDetails.TextContent; release.Link = new Uri(SiteLink + releaseDownloadDetails.GetAttribute("href")); release.Guid = release.Link; release.Details = new Uri(SiteLink + albumNameNode.GetAttribute("href")); // Aug 31 2020, 15:50 try { release.PublishDate = DateTime.ParseExact( releaseDateDetails.GetAttribute("title").Trim(), "MMM dd yyyy, HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal ); } catch (Exception) { } release.Files = ParseUtil.CoerceInt(releaseFileCountDetails.TextContent.Trim()); release.Grabs = ParseUtil.CoerceInt(releaseGrabsDetails.TextContent.Trim()); release.Seeders = ParseUtil.CoerceInt(releaseSeedsCountDetails.TextContent.Trim()); release.Peers = release.Seeders + ParseUtil.CoerceInt(releasePeersCountDetails.TextContent.Trim()); release.Size = ReleaseInfo.GetBytes(releaseSizeDetails.TextContent.Trim()); release.Poster = releaseThumbnailUri; release.Category = releaseNewznabCategory; release.MinimumSeedTime = 259200; // 72 hours // Attempt to find volume factor tag release.DownloadVolumeFactor = 1; release.UploadVolumeFactor = 1; var releaseTags = releaseMediaType.Split('/').Select(tag => tag.Trim()).ToList(); for (var i = releaseTags.Count - 1; i >= 0; i--) { var releaseTag = releaseTags[i]; if (VolumeTagMappings.ContainsKey(releaseTag)) { var volumeFactor = VolumeTagMappings[releaseTag]; release.DownloadVolumeFactor = volumeFactor.DownloadVolumeFactor; release.UploadVolumeFactor = volumeFactor.UploadVolumeFactor; releaseTags.RemoveAt(i); } } // Set title (with volume factor tags stripped) var releaseTagsString = string.Join(" / ", releaseTags); release.Title = String.Format("{0} - {1} [{2}] {3}", releaseArtist, releaseAlbumName, releaseAlbumYear, releaseTagsString); releases.Add(release); } } } } catch (Exception ex) { OnParseError(searchPage.ContentString, ex); } return releases; } } }