diff --git a/README.md b/README.md
index c8daae89a3b57582a8695e17b68d4557181bed8b..ca8dc6d02cc562fda7b1780b4d568c484c775daa 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@ Developer note: The software implements the [Torznab](https://github.com/Sonarr/
  * TorrentLeech
  * TorrentShack
  * TV Chaos UK
+ * World-In-HD
 
 #### Installation on Windows
 
diff --git a/src/Jackett/Content/logos/wihd.png b/src/Jackett/Content/logos/wihd.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6ccd81f4a9b7ed9ff2423ee4d55e00d622ae174
Binary files /dev/null and b/src/Jackett/Content/logos/wihd.png differ
diff --git a/src/Jackett/Indexers/WiHD.cs b/src/Jackett/Indexers/WiHD.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8478b0e5b692a23106032d537131c223358ba6e1
--- /dev/null
+++ b/src/Jackett/Indexers/WiHD.cs
@@ -0,0 +1,705 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using CsQuery;
+using Jackett.Models;
+using Jackett.Models.IndexerConfig.Bespoke;
+using Jackett.Services;
+using Jackett.Utils;
+using Jackett.Utils.Clients;
+using Newtonsoft.Json.Linq;
+using NLog;
+
+namespace Jackett.Indexers
+{
+    /// <summary>
+    /// Provider for WiHD Private French Tracker
+    /// </summary>
+    public class WiHD : BaseIndexer, IIndexer
+    {
+        private string LoginUrl { get { return SiteLink + "login"; } }
+        private string LoginCheckUrl { get { return SiteLink + "login_check"; } }
+        private string SearchUrl { get { return SiteLink + "torrent/ajaxfiltertorrent/"; } }
+        private Dictionary<string, string> emulatedBrowserHeaders = new Dictionary<string, string>();
+        private CQ fDom = null;
+        private bool Latency { get { return ConfigData.Latency.Value; } }
+        private bool DevMode { get { return ConfigData.DevMode.Value; } }
+
+        private ConfigurationDataWiHD ConfigData
+        {
+            get { return (ConfigurationDataWiHD)configData; }
+            set { base.configData = value; }
+        }
+
+        public WiHD(IIndexerManagerService i, IWebClient w, Logger l, IProtectionService ps)
+            : base(
+                name: "WiHD",
+                description: "Your World in High Definition",
+                link: "http://world-in-hd.net/",
+                caps: new TorznabCapabilities(),
+                manager: i,
+                client: w,
+                logger: l,
+                p: ps,
+                downloadBase: "http://world-in-hd.net/torrents/download/",
+                configData: new ConfigurationDataWiHD())
+        {
+            // Clean capabilities
+            TorznabCaps.Categories.Clear();
+
+            // Movies
+            AddCategoryMapping("565af82b1fd35761568b4572", TorznabCatType.MoviesHD);        // 1080P
+            AddCategoryMapping("565af82b1fd35761568b4574", TorznabCatType.MoviesHD);        // 720P
+            AddCategoryMapping("565af82b1fd35761568b4576", TorznabCatType.MoviesHD);        // HDTV
+            AddCategoryMapping("565af82b1fd35761568b4578", TorznabCatType.MoviesBluRay);    // Bluray
+            AddCategoryMapping("565af82b1fd35761568b457a", TorznabCatType.MoviesBluRay);    // Bluray Remux
+            AddCategoryMapping("565af82b1fd35761568b457c", TorznabCatType.Movies3D);        // Bluray 3D
+
+            // TV
+            AddCategoryMapping("565af82d1fd35761568b4587", TorznabCatType.TVHD);            // 1080P
+            AddCategoryMapping("565af82d1fd35761568b4589", TorznabCatType.TVHD);            // 720P
+            AddCategoryMapping("565af82d1fd35761568b458b", TorznabCatType.TVHD);            // HDTV
+            AddCategoryMapping("565af82d1fd35761568b458d", TorznabCatType.TVHD);            // Bluray
+            AddCategoryMapping("565af82d1fd35761568b458f", TorznabCatType.TVHD);            // Bluray Remux
+            AddCategoryMapping("565af82d1fd35761568b4591", TorznabCatType.TVHD);            // Bluray 3D
+
+            // Anime
+            AddCategoryMapping("565af82d1fd35761568b459c", TorznabCatType.TVAnime);         // 1080P
+            AddCategoryMapping("565af82d1fd35761568b459e", TorznabCatType.TVAnime);         // 720P
+            AddCategoryMapping("565af82d1fd35761568b45a0", TorznabCatType.TVAnime);         // HDTV
+            AddCategoryMapping("565af82d1fd35761568b45a2", TorznabCatType.TVAnime);         // Bluray
+            AddCategoryMapping("565af82d1fd35761568b45a4", TorznabCatType.TVAnime);         // Bluray Remux
+            AddCategoryMapping("565af82d1fd35761568b45a6", TorznabCatType.TVAnime);         // Bluray 3D
+
+            // Other
+            AddCategoryMapping("565af82d1fd35761568b45af", TorznabCatType.PC);              // Apps
+            AddCategoryMapping("565af82d1fd35761568b45b1", TorznabCatType.AudioVideo);      // Clips
+            AddCategoryMapping("565af82d1fd35761568b45b3", TorznabCatType.AudioOther);      // Audios Tracks of Movies/TV/Anime
+            AddCategoryMapping("565af82d1fd35761568b45b5", TorznabCatType.TVDocumentary);   // Documentary
+            AddCategoryMapping("565af82d1fd35761568b45b7", TorznabCatType.MoviesBluRay);    // Bluray (ALL)
+        }
+
+        /// <summary>
+        /// Configure our WiHD Provider
+        /// </summary>
+        /// <param name="configJson">Our params in Json</param>
+        /// <returns>Configuration state</returns>
+        public async Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson)
+        {
+            // Retrieve config values set by Jackett's user
+            ConfigData.LoadValuesFromJson(configJson);
+
+            // Check & Validate Config
+            validateConfig();
+
+            // Setting our data for a better emulated browser (maximum security)
+            // TODO: Encoded Content not supported by Jackett at this time
+            // emulatedBrowserHeaders.Add("Accept-Encoding", "gzip, deflate");
+
+            // If we want to simulate a browser
+            if (ConfigData.Browser.Value) {
+
+                // Clean headers
+                emulatedBrowserHeaders.Clear();
+
+                // Inject headers
+                emulatedBrowserHeaders.Add("Accept", ConfigData.HeaderAccept.Value);
+                emulatedBrowserHeaders.Add("Accept-Language", ConfigData.HeaderAcceptLang.Value);
+                emulatedBrowserHeaders.Add("DNT", Convert.ToInt32(ConfigData.HeaderDNT.Value).ToString());
+                emulatedBrowserHeaders.Add("Upgrade-Insecure-Requests", Convert.ToInt32(ConfigData.HeaderUpgradeInsecure.Value).ToString());
+                emulatedBrowserHeaders.Add("User-Agent", ConfigData.HeaderUserAgent.Value);
+            }
+
+
+            // Getting login form to retrieve CSRF token
+            var myRequest = new Utils.Clients.WebRequest()
+            {
+                Url = LoginUrl
+            };
+
+            // Add our headers to request
+            myRequest.Headers = emulatedBrowserHeaders;
+
+            // Get login page
+            var loginPage = await webclient.GetString(myRequest);
+
+            // Retrieving our CSRF token
+            CQ loginPageDom = loginPage.Content;
+            var csrfToken = loginPageDom["input[name=\"_csrf_token\"]"].Last();
+
+            // Building login form data
+            var pairs = new Dictionary<string, string> {
+                { "_csrf_token", csrfToken.Attr("value") },
+                { "_username", ConfigData.Username.Value },
+                { "_password", ConfigData.Password.Value },
+                { "_remember_me", "on" },
+                { "_submit", "" }
+            };
+
+            // Do the login
+            var request = new Utils.Clients.WebRequest(){
+                Cookies = loginPage.Cookies,
+                PostData = pairs,
+                Referer = LoginUrl,
+                Type = RequestType.POST,
+                Url = LoginUrl,
+                Headers = emulatedBrowserHeaders
+            };
+
+            // Perform loggin
+            latencyNow();
+            output("Perform loggin.. with " + LoginCheckUrl);
+            var response = await RequestLoginAndFollowRedirect(LoginCheckUrl, pairs, loginPage.Cookies, true, null, null);
+
+            // Test if we are logged in
+            await ConfigureIfOK(response.Cookies, response.Content != null && response.Content.Contains("/logout"), () => {
+                // Oops, unable to login
+                throw new ExceptionWithConfigData("Failed to login", configData);
+            });
+
+            return IndexerConfigurationStatus.RequiresTesting;
+        }
+
+        /// <summary>
+        /// Execute our search query
+        /// </summary>
+        /// <param name="query">Query</param>
+        /// <returns>Releases</returns>
+        public async Task<IEnumerable<ReleaseInfo>> PerformQuery(TorznabQuery query)
+        {
+            var releases = new List<ReleaseInfo>();
+            var torrentRowList = new List<CQ>();
+            var searchTerm = query.GetQueryString();
+            var searchUrl = SearchUrl;
+
+            // Check cache first so we don't query the server (if search term used or not in dev mode)
+            if(!DevMode && !string.IsNullOrEmpty(searchTerm))
+            {
+                lock (cache)
+                {
+                    // Remove old cache items
+                    CleanCache();
+
+                    var cachedResult = cache.Where(i => i.Query == searchTerm).FirstOrDefault();
+                    if (cachedResult != null)
+                        return cachedResult.Results.Select(s => (ReleaseInfo)s.Clone()).ToArray();
+                }
+            }
+
+            // Add emulated XHR request
+            emulatedBrowserHeaders.Add("X-Requested-With", "XMLHttpRequest");
+
+            // Request our first page
+            latencyNow();
+            var results = await RequestStringWithCookiesAndRetry(buildQuery(searchTerm, query, searchUrl), null, null, emulatedBrowserHeaders);
+            fDom = results.Content;
+
+            try
+            {
+                // Find number of results
+                int nbResults = ParseUtil.CoerceInt(Regex.Match(fDom["div.ajaxtotaltorrentcount"].Text(), @"\d+").Value);
+                output("\nFound " + nbResults + " results for query !");
+
+                // Find torrent rows
+                var firstPageRows = findTorrentRows();
+                output("There are " + firstPageRows.Length + " results on the first page !");
+                torrentRowList.AddRange(firstPageRows.Select(fRow => fRow.Cq()));
+
+                // Calculate numbers of pages available for this search query
+                int pageLinkCount = (int)Math.Ceiling((double)nbResults / firstPageRows.Length);    // Based on number results and number of torrents on first page
+                output("--> Pages available for query: " + pageLinkCount);
+
+                // If we have a term used for search and pagination result superior to one
+                if (!string.IsNullOrWhiteSpace(query.GetQueryString()) && pageLinkCount > 1)
+                {
+                    // Starting with page #2
+                    for (int i = 2; i <= Math.Min(Int32.Parse(ConfigData.Pages.Value), pageLinkCount); i++)
+                    {
+                        output("Processing page #" + i);
+
+                        // Request our page
+                        latencyNow();
+                        results = await RequestStringWithCookiesAndRetry(buildQuery(searchTerm, query, searchUrl, i), null, null, emulatedBrowserHeaders);
+
+                        // Assign response
+                        fDom = results.Content;
+
+                        // Process page results
+                        var additionalPageRows = findTorrentRows();
+                        torrentRowList.AddRange(additionalPageRows.Select(fRow => fRow.Cq()));
+                    }
+                }
+
+                // Loop on results
+                foreach (CQ tRow in torrentRowList)
+                {
+                    output("\n=>> Torrent #" + (releases.Count + 1));
+
+                    // Release Name
+                    string name = tRow.Find(".torrent-h3 > h3 > a").Attr("title").ToString();
+                    output("Release: " + name);
+
+                    // Category
+                    string categoryID = tRow.Find(".category > img").Attr("src").Split('/').Last().ToString();
+                    string categoryName = tRow.Find(".category > img").Attr("title").ToString();
+                    output("Category: " + MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName)) + " (" + categoryName + ")");
+
+                    // Uploader
+                    string uploader = tRow.Find(".uploader > span > a").Attr("title").ToString();
+                    output("Uploader: " + uploader);
+
+                    // Seeders
+                    int seeders = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".seeders")[0].LastChild.ToString(), @"\d+").Value);
+                    output("Seeders: " + seeders);
+
+                    // Leechers
+                    int leechers = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".leechers")[0].LastChild.ToString(), @"\d+").Value);
+                    output("Leechers: " + leechers);
+
+                    // Completed
+                    int completed = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".completed")[0].LastChild.ToString(), @"\d+").Value);
+                    output("Completed: " + completed);
+
+                    // Comments
+                    int comments = ParseUtil.CoerceInt(Regex.Match(tRow.Find(".comments")[0].LastChild.ToString(), @"\d+").Value);
+                    output("Comments: " + comments);
+
+                    // Size & Publish Date
+                    string infosData = tRow.Find(".torrent-h3 > span")[0].LastChild.ToString().Trim();
+                    IList<string> infosList = infosData.Split('-').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
+
+                    // --> Size
+                    var size = ReleaseInfo.GetBytes(infosList[1].Replace("Go", "gb").Replace("Mo", "mb").Replace("Ko", "kb"));
+                    output("Size: " + infosList[1] + " (" + size + " bytes)");
+
+                    // --> Publish Date
+                    IList<string> clockList = infosList[0].Replace("Il y a", "").Split(',').Select(s => s.Trim()).Where(s => s != String.Empty).ToList();
+                    var clock = agoToDate(clockList);
+                    output("Released on: " + clock.ToString());
+
+                    // Torrent Details URL
+                    string details = tRow.Find(".torrent-h3 > h3 > a").Attr("href").ToString().TrimStart('/');
+                    Uri detailsLink = new Uri(SiteLink + details);
+                    output("Details: " + detailsLink.AbsoluteUri);
+
+                    // Torrent Comments URL
+                    Uri commentsLink = new Uri(SiteLink + details + "#tab_2");
+                    output("Comments: " + commentsLink.AbsoluteUri);
+
+                    // Torrent Download URL
+                    string download = tRow.Find(".download-item > a").Attr("href").ToString().TrimStart('/');
+                    Uri downloadLink = new Uri(SiteLink + download);
+                    output("Download: " + downloadLink.AbsoluteUri);
+
+                    // Building release infos
+                    var release = new ReleaseInfo();
+                    release.Category = MapTrackerCatToNewznab(mediaToCategory(categoryID, categoryName));
+                    release.Title = name;
+                    release.Seeders = seeders;
+                    release.Peers = seeders + leechers;
+                    release.MinimumRatio = 1;
+                    release.MinimumSeedTime = 345600;
+                    release.PublishDate = clock;
+                    release.Size = size;
+                    release.Guid = detailsLink;
+                    release.Comments = commentsLink;
+                    release.Link = downloadLink;
+                    releases.Add(release);
+                }
+
+            }
+            catch (Exception ex)
+            {
+                OnParseError("Error, unable to parse result", ex);
+            }
+
+            // Remove our XHR request header
+            emulatedBrowserHeaders.Remove("X-Requested-With");
+
+            // Return found releases
+            return releases;
+        }
+
+        /// <summary>
+        /// Build query to process
+        /// </summary>
+        /// <param name="term">Term to search</param>
+        /// <param name="query">Torznab Query for categories mapping</param>
+        /// <param name="url">Search url for provider</param>
+        /// <param name="page">Page number to request</param>
+        /// <param name="exclu">Exclusive state</param>
+        /// <param name="freeleech">Freeleech state</param>
+        /// <param name="reseed">Reseed state</param>
+        /// <returns>URL to query for parsing and processing results</returns>
+        private string buildQuery(string term, TorznabQuery query, string url, int page = 1)
+        {
+            var parameters = new NameValueCollection();
+            List<string> categoriesList = MapTorznabCapsToTrackers(query);
+            string categories = null;
+
+            if (string.IsNullOrWhiteSpace(term))
+            {
+                // If no search string provided, use default (for testing purposes)
+                term = "the walking dead";
+            }
+
+            // Encode & Add search term to URL
+            url += Uri.EscapeDataString(term);
+
+            // Check if we are processing a new page
+            if (page > 1)
+            {
+                // Adding page number to query
+                url += "/" + page.ToString();
+            }
+
+            // Adding interrogation point
+            url += "?";
+
+            // Building our tracker query
+            parameters.Add("exclu", Convert.ToInt32(ConfigData.Exclusive.Value).ToString());
+            parameters.Add("freeleech", Convert.ToInt32(ConfigData.Freeleech.Value).ToString());
+            parameters.Add("reseed", Convert.ToInt32(ConfigData.Reseed.Value).ToString());
+
+            // Loop on Categories needed
+            foreach (string category in categoriesList)
+            {
+                // If last, build !
+                if(categoriesList.Last() == category)
+                {
+                    // Adding previous categories to URL with latest category
+                    parameters.Add(Uri.EscapeDataString("subcat[]"), category + categories);
+                }
+                else
+                {
+                    // Build categories parameter
+                    categories += "&" + Uri.EscapeDataString("subcat[]") + "=" + category;
+                }
+            }
+
+            // Add timestamp as a query param (for no caching)
+            parameters.Add("_", UnixTimeNow().ToString());
+
+            // Building our query -- Cannot use GetQueryString due to UrlEncode (generating wrong subcat[] param)
+            url += string.Join("&", parameters.AllKeys.Select(a => a + "=" + parameters[a]));
+
+            output("Builded query for \"" + term + "\"... with " + url);
+
+            // Return our search url
+            return url;
+        }
+
+        /// <summary>
+        /// Generate a random fake latency to avoid detection on tracker side
+        /// </summary>
+        private void latencyNow()
+        {
+            // Need latency ?
+            if(Latency)
+            {
+                var random = new Random(DateTime.Now.Millisecond);
+                int waiting = random.Next(Convert.ToInt32(ConfigData.LatencyStart.Value), Convert.ToInt32(ConfigData.LatencyEnd.Value));
+                output("Latency Faker => Sleeping for " + waiting + " ms...");
+                // Sleep now...
+                System.Threading.Thread.Sleep(waiting);
+            }
+        }
+
+        /// <summary>
+        /// Generate an UTC Unix TimeStamp
+        /// </summary>
+        /// <returns>Unix TimeStamp</returns>
+        private long UnixTimeNow()
+        {
+            var timeSpan = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
+            return (long)timeSpan.TotalSeconds;
+        }
+
+        /// <summary>
+        /// Find torrent rows in search pages
+        /// </summary>
+        /// <returns>JQuery Object</returns>
+        private CQ findTorrentRows()
+        {
+            // Return all occurencis of torrents found
+            return fDom[".torrent-item"];
+        }
+
+        /// <summary>
+        /// Convert Ago date to DateTime
+        /// </summary>
+        /// <param name="clockList"></param>
+        /// <returns>A DateTime</returns>
+        private DateTime agoToDate(IList<string> clockList)
+        {
+            DateTime release = DateTime.Now;
+            foreach(var ago in clockList)
+            {
+                // Check for years
+                if(ago.Contains("Années") || ago.Contains("Année"))
+                {
+                    // Number of years to remove
+                    int years = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
+                    // Removing
+                    release = release.AddYears(-years);
+
+                    continue;
+                }
+                // Check for months
+                else if (ago.Contains("Mois"))
+                {
+                    // Number of months to remove
+                    int months = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
+                    // Removing
+                    release = release.AddMonths(-months);
+
+                    continue;
+                }
+                // Check for days
+                else if (ago.Contains("Jours") || ago.Contains("Jour"))
+                {
+                    // Number of days to remove
+                    int days = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
+                    // Removing
+                    release = release.AddDays(-days);
+
+                    continue;
+                }
+                // Check for hours
+                else if (ago.Contains("Heures") || ago.Contains("Heure"))
+                {
+                    // Number of hours to remove
+                    int hours = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
+                    // Removing
+                    release = release.AddHours(-hours);
+
+                    continue;
+                }
+                // Check for minutes
+                else if (ago.Contains("Minutes") || ago.Contains("Minute"))
+                {
+                    // Number of minutes to remove
+                    int minutes = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
+                    // Removing
+                    release = release.AddMinutes(-minutes);
+
+                    continue;
+                }
+                // Check for seconds
+                else if (ago.Contains("Secondes") || ago.Contains("Seconde"))
+                {
+                    // Number of seconds to remove
+                    int seconds = ParseUtil.CoerceInt(Regex.Match(ago.ToString(), @"\d+").Value);
+                    // Removing
+                    release = release.AddSeconds(-seconds);
+
+                    continue;
+                }
+                else
+                {
+                    output("Unable to detect release date of torrent", "error");
+                    //throw new Exception("Unable to detect release date of torrent");
+                }
+            }
+            return release;
+        }
+
+        /// <summary>
+        /// Retrieve category ID from media ID
+        /// </summary>
+        /// <param name="media">Media ID</param>
+        /// <returns>Category ID</returns>
+        private string mediaToCategory(string media, string name)
+        {
+            // Declare our Dictionnary -- Media ID (key) <-> Category ID (value)
+            Dictionary<string, string> dictionary = new Dictionary<string, string>();
+
+            // Movies
+            dictionary.Add("565af82b1fd35761568b4573", "565af82b1fd35761568b4572");         // 1080P
+            dictionary.Add("565af82b1fd35761568b4575", "565af82b1fd35761568b4574");         // 720P
+            dictionary.Add("565af82b1fd35761568b4577", "565af82b1fd35761568b4576");         // HDTV
+            dictionary.Add("565af82b1fd35761568b4579", "565af82b1fd35761568b4578");         // Bluray
+            dictionary.Add("565af82b1fd35761568b457b", "565af82b1fd35761568b457a");         // Bluray Remux
+            dictionary.Add("565af82b1fd35761568b457d", "565af82b1fd35761568b457c");         // Bluray 3D
+
+            // TV
+            dictionary.Add("565af82d1fd35761568b4588", "565af82d1fd35761568b4587");         // 1080P
+            dictionary.Add("565af82d1fd35761568b458a", "565af82d1fd35761568b4589");         // 720P
+            dictionary.Add("565af82d1fd35761568b458c", "565af82d1fd35761568b458b");         // HDTV
+            dictionary.Add("565af82d1fd35761568b458e", "565af82d1fd35761568b458d");         // Bluray
+            dictionary.Add("565af82d1fd35761568b4590", "565af82d1fd35761568b458f");         // Bluray Remux
+            dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b4591");         // Bluray 3D
+
+            // Anime
+            dictionary.Add("565af82d1fd35761568b459d", "565af82d1fd35761568b459c");         // 1080P
+            dictionary.Add("565af82d1fd35761568b459f", "565af82d1fd35761568b459e");         // 720P
+            dictionary.Add("565af82d1fd35761568b45a1", "565af82d1fd35761568b45a0");         // HDTV
+            dictionary.Add("565af82d1fd35761568b45a3", "565af82d1fd35761568b45a2");         // Bluray
+            dictionary.Add("565af82d1fd35761568b45a5", "565af82d1fd35761568b45a4");         // Bluray Remux
+            // BUG ~~ Media ID for Anime BR 3D is same as TV BR 3D ~~
+            //dictionary.Add("565af82d1fd35761568b4592", "565af82d1fd35761568b45a6");       // Bluray 3D
+
+            // Other
+            dictionary.Add("565af82d1fd35761568b45b0", "565af82d1fd35761568b45af");         // Apps
+            dictionary.Add("565af82d1fd35761568b45b2", "565af82d1fd35761568b45b1");         // Clips
+            dictionary.Add("565af82d1fd35761568b45b4", "565af82d1fd35761568b45b3");         // Audios Tracks of Movies/TV/Anime
+            dictionary.Add("565af82d1fd35761568b45b6", "565af82d1fd35761568b45b5");         // Documentary
+            dictionary.Add("565af82d1fd35761568b45b8", "565af82d1fd35761568b45b7");         // Bluray (ALL)
+
+            // Check if we know this media ID
+            if (dictionary.ContainsKey(media))
+            {
+                // Due to a bug on tracker side, check for a specific id/name as image is same for TV/Anime BR 3D
+                if(media == "565af82d1fd35761568b4592" && name == "Animations - Bluray 3D")
+                {
+                    // If it's an Anime BR 3D
+                    return "565af82d1fd35761568b45a6";
+                }
+                else
+                {
+                    // Return category ID for media ID
+                    return dictionary[media];
+                }
+            }
+            else
+            {
+                // Media ID unknown
+                throw new Exception("Media ID Unknow !");
+            }
+        }
+
+        /// <summary>
+        /// Output message for logging or developpment (console)
+        /// </summary>
+        /// <param name="message">Message to output</param>
+        /// <param name="level">Level for Logger</param>
+        private void output(string message, string level = "debug")
+        {
+            // Check if we are in dev mode
+            if(DevMode)
+            {
+                // Output message to console
+                Console.WriteLine(message);
+            }
+            else
+            {
+                // Send message to logger with level
+                switch (level)
+                {
+                    default:
+                        goto case "debug";
+                    case "debug":
+                        // Only if Debug Level Enabled on Jackett
+                        if(Engine.Logger.IsDebugEnabled) {
+                            logger.Debug(message);
+                        }
+                        break;
+                    case "info":
+                        logger.Info(message);
+                        break;
+                    case "error":
+                        logger.Error(message);
+                        break;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Validate Config entered by user on Jackett
+        /// </summary>
+        private void validateConfig()
+        {
+            // Check Username Setting
+            if (string.IsNullOrEmpty(ConfigData.Username.Value))
+            {
+                throw new ExceptionWithConfigData("You must provide a username for this tracker to login !", ConfigData);
+            }
+
+            // Check Password Setting
+            if (string.IsNullOrEmpty(ConfigData.Password.Value))
+            {
+                throw new ExceptionWithConfigData("You must provide a password with your username for this tracker to login !", ConfigData);
+            }
+
+            // Check Max Page Setting
+            if (!string.IsNullOrEmpty(ConfigData.Pages.Value))
+            {
+                try
+                {
+                    output("Settings -- Max Pages => " + Convert.ToInt32(ConfigData.Pages.Value));
+                }
+                catch (Exception)
+                {
+                    throw new ExceptionWithConfigData("Please enter a numeric maximum number of pages to crawl !", ConfigData);
+                }
+            }
+            else
+            {
+                throw new ExceptionWithConfigData("Please enter a maximum number of pages to crawl !", ConfigData);
+            }
+
+            // Check Latency Setting
+            if (ConfigData.Latency.Value)
+            {
+                // Check Latency Start Setting
+                if (!string.IsNullOrEmpty(ConfigData.LatencyStart.Value))
+                {
+                    try
+                    {
+                        output("Settings -- Latency Start => " + Convert.ToInt32(ConfigData.LatencyStart.Value));
+                    }
+                    catch (Exception)
+                    {
+                        throw new ExceptionWithConfigData("Please enter a numeric latency start in ms !", ConfigData);
+                    }
+                }
+                else
+                {
+                    throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a start latency !", ConfigData);
+                }
+
+                // Check Latency End Setting
+                if (!string.IsNullOrEmpty(ConfigData.LatencyEnd.Value))
+                {
+                    try
+                    {
+                        output("Settings -- Latency End => " + Convert.ToInt32(ConfigData.LatencyEnd.Value));
+                    }
+                    catch (Exception)
+                    {
+                        throw new ExceptionWithConfigData("Please enter a numeric latency end in ms !", ConfigData);
+                    }
+                }
+                else
+                {
+                    throw new ExceptionWithConfigData("Latency Simulation enabled, Please enter a end latency !", ConfigData);
+                }
+            }
+
+            // Check Browser Setting
+            if (ConfigData.Browser.Value)
+            {
+                // Check ACCEPT header Setting
+                if (string.IsNullOrEmpty(ConfigData.HeaderAccept.Value))
+                {
+                    throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT header !", ConfigData);
+                }
+
+                // Check ACCEPT-LANG header Setting
+                if (string.IsNullOrEmpty(ConfigData.HeaderAcceptLang.Value))
+                {
+                    throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an ACCEPT-LANG header !", ConfigData);
+                }
+
+                // Check USER-AGENT header Setting
+                if (string.IsNullOrEmpty(ConfigData.HeaderUserAgent.Value))
+                {
+                    throw new ExceptionWithConfigData("Browser Simulation enabled, Please enter an USER-AGENT header !", ConfigData);
+                }
+            }
+        }
+    }
+}
diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj
index 628b554dd3767df9bee3d5d89769acf3519ed7ed..b90f7b634f0fdc7cbdacaa1f227f843a77a4edf1 100644
--- a/src/Jackett/Jackett.csproj
+++ b/src/Jackett/Jackett.csproj
@@ -207,10 +207,12 @@
     <Compile Include="Indexers\ImmortalSeed.cs" />
     <Compile Include="Indexers\FileList.cs" />
     <Compile Include="Indexers\Abstract\AvistazTracker.cs" />
+    <Compile Include="Indexers\WiHD.cs" />
     <Compile Include="Indexers\XSpeeds.cs" />
     <Compile Include="Models\GitHub\Asset.cs" />
     <Compile Include="Models\GitHub\Release.cs" />
     <Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataBlueTigers.cs" />
+    <Compile Include="Models\IndexerConfig\Bespoke\ConfigurationDataWiHD.cs" />
     <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithFilter.cs" />
     <Compile Include="Models\IndexerConfig\ConfigurationDataAPIKey.cs" />
     <Compile Include="Models\IndexerConfig\ConfigurationDataBasicLoginWithRSSAndDisplay.cs" />
@@ -562,6 +564,7 @@
     <Content Include="Content\logos\tvchaosuk.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="Content\logos\wihd.png" />
     <Content Include="Content\logos\xspeeds.png" />
     <Content Include="Content\setup_indexer.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -626,4 +629,4 @@
     </PropertyGroup>
     <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
   </Target>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataWiHD.cs b/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataWiHD.cs
new file mode 100644
index 0000000000000000000000000000000000000000..95075e2f83ab8711df6a280bdbee92a2ea829f85
--- /dev/null
+++ b/src/Jackett/Models/IndexerConfig/Bespoke/ConfigurationDataWiHD.cs
@@ -0,0 +1,55 @@
+namespace Jackett.Models.IndexerConfig.Bespoke
+{
+    class ConfigurationDataWiHD : ConfigurationData
+    {
+        public DisplayItem CredentialsWarning { get; private set; }
+        public StringItem Username { get; private set; }
+        public StringItem Password { get; private set; }
+        public DisplayItem PagesWarning { get; private set; }
+        public StringItem Pages { get; private set; }
+        public BoolItem Exclusive { get; private set; }
+        public BoolItem Freeleech { get; private set; }
+        public BoolItem Reseed { get; private set; }
+        public DisplayItem SecurityWarning { get; private set; }
+        public BoolItem Latency { get; private set; }
+        public BoolItem Browser { get; private set; }
+        public DisplayItem LatencyWarning { get; private set; }
+        public StringItem LatencyStart { get; private set; }
+        public StringItem LatencyEnd { get; private set; }
+        public DisplayItem HeadersWarning { get; private set; }
+        public StringItem HeaderAccept { get; private set; }
+        public StringItem HeaderAcceptLang { get; private set; }
+        public BoolItem HeaderDNT { get; private set; }
+        public BoolItem HeaderUpgradeInsecure { get; private set; }
+        public StringItem HeaderUserAgent { get; private set; }
+        public DisplayItem DevWarning { get; private set; }
+        public BoolItem DevMode { get; private set; }
+
+        public ConfigurationDataWiHD()
+            : base()
+        {
+            CredentialsWarning = new DisplayItem("<b>Credentials Configuration</b> (<i>Private Tracker</i>),<br /><br /> <ul><li><b>Username</b> is your account name on this tracker.</li><li><b>Password</b> is your password associated to your account name.</li></ul>") { Name = "Credentials" };
+            Username = new StringItem { Name = "Username (Required)", Value = "" };
+            Password = new StringItem { Name = "Password (Required)", Value = "" };
+            PagesWarning = new DisplayItem("<b>Preferences Configuration</b> (<i>Tweak your search settings</i>),<br /><br /> <ul><li><b>Max Pages to Process</b> let you specify how many page (max) Jackett can process when doing a search. Setting a value <b>higher than 4 is dangerous</b> for you account ! (<b>Result of too many requests to tracker...that <u>will be suspect</u></b>).</li><li><b>Exclusive Only</b> let you search <u>only</u> for torrents which are marked Exclusive.</li><li><b>Freeleech Only</b> let you search <u>only</u> for torrents which are marked Freeleech.</li><li><b>Reseed Only</b> let you search <u>only</u> for torrents which need to be seeded.</li></ul>") { Name  = "Preferences" };
+            Pages = new StringItem { Name = "Max Pages to Process (Required)", Value = "4" };
+            Exclusive = new BoolItem() { Name = "Exclusive Only (Optional)", Value = false };
+            Freeleech = new BoolItem() { Name = "Freeleech Only (Optional)", Value = false };
+            Reseed = new BoolItem() { Name = "Reseed Needed Only (Optional)", Value = false };
+            SecurityWarning = new DisplayItem("<b>Security Configuration</b> (<i>Read this area carefully !</i>),<br /><br /> <ul><li><b>Latency Simulation</b> will simulate human browsing with Jacket by pausing Jacket for an random time between each request, to fake a real content browsing.</li><li><b>Browser Simulation</b> will simulate a real human browser by injecting additionals headers when doing requests to tracker.</li></ul>") { Name = "Security" };
+            Latency = new BoolItem() { Name = "Latency Simulation (Optional)", Value = true };
+            Browser = new BoolItem() { Name = "Browser Simulation (Optional)", Value = true };
+            LatencyWarning = new DisplayItem("<b>Latency Configuration</b> (<i>Required if latency simulation enabled</i>),<br /><br/> <ul><li>By filling this range, <b>Jackett will make a random timed pause</b> <u>between requests</u> to tracker <u>to simulate a real browser</u>.</li><li>MilliSeconds <b>only</b></li></ul>") { Name = "Simulate Latency" };
+            LatencyStart = new StringItem { Name = "Minimum Latency (ms)", Value = "1589" };
+            LatencyEnd = new StringItem { Name = "Maximum Latency (ms)", Value = "3674" };
+            HeadersWarning = new DisplayItem("<b>Browser Headers Configuration</b> (<i>Required if browser simulation enabled</i>),<br /><br /> <ul><li>By filling these fields, <b>Jackett will inject headers</b> with your values <u>to simulate a real browser</u>.</li><li>You can get <b>your browser values</b> here: <a href='https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending' target='blank'>www.whatismybrowser.com</a></li></ul><br /><i><b>Note that</b> some headers are not necessary because they are injected automatically by this provider such as Accept_Encoding, Connection, Host or X-Requested-With</i>") { Name = "Injecting headers" };
+            HeaderAccept = new StringItem { Name = "Accept", Value = "" };
+            HeaderAcceptLang = new StringItem { Name = "Accept-Language", Value = "" };
+            HeaderDNT = new BoolItem { Name = "DNT", Value = false };
+            HeaderUpgradeInsecure = new BoolItem { Name = "Upgrade-Insecure-Requests", Value = false };
+            HeaderUserAgent = new StringItem { Name = "User-Agent", Value = "" };
+            DevWarning = new DisplayItem("<b>Devlopement Facility</b> (<i>For Developers ONLY</i>),<br /><br /> <ul><li>By enabling devlopement mode, <b>Jackett will bypass his cache</b> and will <u>output debug messages to console</u> instead of his log file.</li></ul>") { Name = "Devlopement" };
+            DevMode = new BoolItem { Name = "Enable DEV MODE (Developers ONLY)", Value = false };
+        }
+    }
+}