public DLSiteMetadata(IPlayniteAPI api) : base(api)
        {
            Settings = new DLSiteMetadataSettings(this);

            Task.Run(() =>
            {
                var dataDir = GetPluginUserDataPath();
                var count   = DLSiteGenres.LoadGenres(dataDir);
                Logger.Info($"Loaded {count} DLSite genres");
            });
        }
        public DLSiteMetadata(IPlayniteAPI api) : base(api)
        {
            Settings = new DLSiteMetadataSettings(this);

            Task.Run(() =>
            {
                var dataDir = Path.Combine(api.Paths.ExtensionsDataPath, Id.ToString());
                var count   = DLSiteGenres.LoadGenres(dataDir);
                Logger.Info($"Loaded {count} DLSite genres");
            });
        }
        private List <MetadataField> GetAvailableFields()
        {
            var game = _options.GameData;
            var list = new List <MetadataField>();

            var name = game.Name;

            if (name.IsEmpty())
            {
                var dlSiteLink = game.Links.FirstOrDefault(x =>
                                                           x.Name.Equals("DLSite", StringComparison.InvariantCultureIgnoreCase));
                if (dlSiteLink == null)
                {
                    return(list);
                }
                name = dlSiteLink.Url;
            }

            var id = name;

IDCheck:
            if (!id.StartsWith("RJ", StringComparison.InvariantCultureIgnoreCase) &&
                !id.StartsWith("RE", StringComparison.InvariantCultureIgnoreCase))
            {
                if (id.StartsWith(Consts.RootENG) || id.StartsWith(Consts.RootJPN))
                {
                    //https://www.dlsite.com/ecchi-eng/work/=/product_id/RE234198.html
                    var root = id.StartsWith(Consts.RootENG) ? Consts.RootENG : Consts.RootJPN;
                    id = id.Replace(root, "");
                    //work/=/product_id/{id}.html
                    id = id.Replace(".html", "").Replace("work/=/product_id/", "");
                    goto IDCheck;
                }

                var dlSiteLink = game.Links.FirstOrDefault(x =>
                                                           x.Name.Equals("DLSite", StringComparison.InvariantCultureIgnoreCase));

                if (dlSiteLink != null)
                {
                    id = dlSiteLink.Url;
                    goto IDCheck;
                }

                Logger.Warn($"Trying to get metadata for {id} but it does not start with RJ/RE!");
                return(list);
            }

            _game = DLSiteGame.LoadGame(id, Logger).Result;

            Task.Run(() =>
            {
                DLSiteGenres.AddGenres(_game.Genres);
                var dataDir = Path.Combine(_plugin.PlayniteApi.Paths.ExtensionsDataPath, _plugin.Id.ToString());
                DLSiteGenres.SaveGenres(dataDir);
            });

            list.Add(MetadataField.Links);

            if (!_game.Name.IsEmpty())
            {
                list.Add(MetadataField.Name);
            }

            if (!_game.Circle.IsEmpty())
            {
                list.Add(MetadataField.Developers);
                list.Add(MetadataField.Publishers);
            }

            if (!_game.Description.IsEmpty())
            {
                list.Add(MetadataField.Description);
            }

            if (!_game.Release.IsEmpty())
            {
                list.Add(MetadataField.ReleaseDate);
            }

            if (_game.Genres != null && _game.Genres.Count > 0)
            {
                list.Add(MetadataField.Genres);
            }

            if (_game.ImageURLs != null && _game.ImageURLs.Count > 0)
            {
                list.Add(MetadataField.CoverImage);
                if (_game.ImageURLs.Count > 1)
                {
                    list.Add(MetadataField.BackgroundImage);
                }
            }

            //TODO: Age Ratings
            //TODO: Platform

            return(list);
        }
        public static async Task <DLSiteGame> LoadGame(string id, ILogger logger)
        {
            var isEnglish = id.StartsWith("RE");
            var url       = Consts.GetWorkURL(id, isEnglish);

            var web      = new HtmlWeb();
            var document = await web.LoadFromWebAsync(url);

            if (document == null)
            {
                return(null);
            }

            var node = document.DocumentNode;

            //logger.Info(node.InnerHtml);

            var game = new DLSiteGame {
                DLSiteLink = url
            };

            if (node.TryGetInnerText(
                    "//div[@id='top_wrapper']/div[@class='base_title_br clearfix']/h1[@id='work_name']/a", logger, "Name",
                    id, out var name))
            {
                game.Name = name;
            }

            var imageNodes =
                node.SelectNodes(
                    "//div[@id='work_header']/div[@id='work_left']/div/div[@class='product-slider']/div[@class='product-slider-data']/div");

            if (!imageNodes.IsNullOrEmpty(logger, "Images", id))
            {
                game.ImageURLs = imageNodes.Select(x =>
                {
                    var src = x.GetValue("data-src");
                    if (src.StartsWith("//"))
                    {
                        src = $"https:{src}";
                    }
                    return(src);
                }).NotNull().ToList();
            }

            //TODO: ratings
            //ratings require script execution

            /*var ratingsNode =
             *  node.SelectSingleNode(
             *      "//div[@id='work_right']/div[@class='work_right_info']/div[@class='work_right_info_item'][2]/dl[@class='work_right_info_title']//span[@class='point average_count']");
             * if(ratingsNode == null)
             *  logger.Warn($"Found no ratings node for {id}!");
             * else
             * {
             *  var sRating = ratingsNode.DecodeInnerText();
             *  if(sRating.IsEmpty())
             *      logger.Warn($"Rating for {id} is empty!");
             *  else
             *  {
             *      if(!double.TryParse(sRating, out var rating))
             *          logger.Warn($"Unable to parse rating {sRating} to double!");
             *      else
             *          game.Rating = rating;
             *  }
             * }*/

            var workRightNode = node.SelectSingleNode("//div[@id='work_right']/div[@id='work_right_inner']");

            if (workRightNode.IsNull(logger, "Work Right Div", id))
            {
                return(game);
            }

            var circleNode = workRightNode.SelectSingleNode("//div[@id='work_right_name']/table[@id='work_maker']/tr/td/span[@class='maker_name']/a");

            if (!circleNode.IsNull(logger, "Circle", id))
            {
                var sCircle = circleNode.DecodeInnerText();
                if (!sCircle.IsEmpty(logger, "Circle", id))
                {
                    game.Circle = sCircle;
                }

                var sCircleLink = circleNode.GetValue("href");
                if (!sCircleLink.IsEmpty(logger, "Circle Link", id))
                {
                    game.CircleLink = sCircleLink;
                }
            }

            var descriptionNode = node.SelectSingleNode("//div[@class='work_parts_container']/div[@class='work_parts type_text']/div[@class='work_parts_area']/p");

            if (!descriptionNode.IsNull(logger, "Description", id))
            {
                var sDescription = descriptionNode.InnerHtml;
                if (!sDescription.IsEmpty(logger, "Description", id))
                {
                    game.Description = HttpUtility.HtmlDecode(sDescription);
                }
            }

            var tableChildren = workRightNode.SelectNodes("//table[@id='work_outline']//tr");

            if (tableChildren.IsNullOrEmpty(logger, "Table", id))
            {
                return(game);
            }

            Dictionary <string, HtmlNode> dic = tableChildren.ToDictionary(x =>
            {
                var th = x.SelectSingleNode("th");
                return(th.DecodeInnerText());
            }, x => x.SelectSingleNode("td"));

            dic.Do(x =>
            {
                var key = x.Key;
                var td  = x.Value;

                if (key == Consts.GetReleaseTranslation(isEnglish))
                {
                    var dateNode = td.SelectSingleNode("a");
                    if (!dateNode.IsNull(logger, "Release Date", id))
                    {
                        var sDate = dateNode.DecodeInnerText();
                        if (!sDate.IsEmpty(logger, "Release Date", id))
                        {
                            game.Release = sDate;
                        }
                    }

                    return;
                }

                if (key == Consts.GetLastModifiedTranslation(isEnglish))
                {
                    var sLastModified = td.DecodeInnerText();
                    if (!sLastModified.IsEmpty(logger, "Last Modified", id))
                    {
                        game.LastModified = sLastModified;
                    }
                    return;
                }

                if (key == Consts.GetAgeRatingsTranslation(isEnglish))
                {
                    var ratingNode = td.SelectSingleNode("div[@class='work_genre']/a/span");
                    if (!ratingNode.IsNull(logger, "Age Rating", id))
                    {
                        var sRating = ratingNode.DecodeInnerText();
                        if (!sRating.IsEmpty(logger, "Age Rating", id))
                        {
                            game.AgeRating = Utils.ToAgeRating(sRating);
                        }
                    }

                    return;
                }

                if (key == Consts.GetWorkFormatTranslation(isEnglish))
                {
                    var formatNodes = td.SelectNodes("div[@class='work_genre']/a/span");
                    if (!formatNodes.IsNullOrEmpty(logger, "Work Format", id))
                    {
                        game.WorkFormats = formatNodes.Select(y => y.DecodeInnerText()).NotNull().ToList();
                    }

                    return;
                }

                if (key == Consts.GetFileFormatTranslation(isEnglish))
                {
                    var fileFormatNode = td.SelectSingleNode("div[@class='work_genre']/a/span");
                    if (!fileFormatNode.IsNull(logger, "File Format", id))
                    {
                        var sFileFormat = fileFormatNode.DecodeInnerText();
                        if (!sFileFormat.IsEmpty(logger, "File Format", id))
                        {
                            game.FileFormat = sFileFormat;
                        }
                    }

                    return;
                }

                if (key == Consts.GetGenreTranslation(isEnglish))
                {
                    var genreNodes = td.SelectNodes("div[@class='main_genre']/a");
                    if (!genreNodes.IsNullOrEmpty(logger, "Genres", id))
                    {
                        game.Genres = genreNodes.Select(genreNode =>
                        {
                            var genreUrl = genreNode.GetValue("href");
                            var genreID  = isEnglish ? DLSiteGenre.GetENGID(genreUrl) : DLSiteGenre.GetJPNID(genreUrl);
                            if (genreID == -1)
                            {
                                logger.Error($"Could not get ID from {genreUrl}");
                                return(null);
                            }

                            if (DLSiteGenres.TryGetGenre(genreID, out var cachedGenre))
                            {
                                if (cachedGenre.ENG != null)
                                {
                                    return(cachedGenre);
                                }
                            }

                            var genre     = new DLSiteGenre(genreID);
                            var genreName = genreNode.DecodeInnerText();
                            if (isEnglish)
                            {
                                genre.ENG = genreName;
                            }
                            else
                            {
                                genre.JPN         = genreName;
                                var resultConvert = DLSiteGenres.ConvertTo(genre, logger, isEnglish);
                                if (string.IsNullOrEmpty(resultConvert))
                                {
                                    logger.Error($"Unable to convert {genreName} to English genre!");
                                    return(null);
                                }

                                genre.ENG = resultConvert;
                            }

                            return(genre);
                        }).NotNull().ToList();
                    }

                    return;
                }

                if (key == Consts.GetFileSizeTranslation(isEnglish))
                {
                    var fileSizeNode = td.SelectSingleNode("div[@class='main_genre']");
                    if (!fileSizeNode.IsNull(logger, "File Size", id))
                    {
                        var sFileSize = fileSizeNode.DecodeInnerText();
                        if (!sFileSize.IsEmpty(logger, "File Size", id))
                        {
                            game.FileSize = sFileSize;
                        }
                    }

                    return;
                }

                logger.Warn($"Unknown key: {key}");
            });

            return(game);
        }