public static LogError ( string message ) : void | ||
message | string | |
return | void |
/// <summary> /// Defines the name and version information based on attributes pulled from the assembly file. /// </summary> private static void DefineNameAndVersion() { try { var assembly = typeof(ProductInfo).GetTypeInfo().Assembly; var prod = assembly.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(AssemblyProductAttribute)); var ver = assembly.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(AssemblyInformationalVersionAttribute)); var fVer = assembly.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(AssemblyFileVersionAttribute)); AssemblyVersion = assembly.GetName().Version; if (prod != null && prod.ConstructorArguments.Count > 0) { Name = prod.ConstructorArguments[0].Value as string ?? Name; } if (ver != null && ver.ConstructorArguments.Count > 0) { Version = ver.ConstructorArguments[0].Value as string ?? Version; } if (fVer != null && fVer.ConstructorArguments.Count > 0) { string fileVersionString = fVer.ConstructorArguments[0].Value as string ?? "0.0.0.1"; FileVersion = new Version(fileVersionString); } } catch (Exception e) { Logger2.LogError(e, "Attempt to define the name and version of the program failed."); } }
/// <summary> /// Get the HTML document for the specified URI. /// </summary> /// <param name="uri">The URI to load.</param> /// <param name="token">The cancellation token.</param> /// <returns>Returns the requested page, if found. Otherwise, null.</returns> private async static Task <HtmlDocument?> GetDocumentAsync(Uri uri, IPageProvider pageProvider, CancellationToken token) { HtmlDocument?page = null; try { page = await pageProvider.GetHtmlDocumentAsync(uri.AbsoluteUri, uri.Host, CachingMode.UseCache, ShouldCache.Yes, SuppressNotifications.Yes, token) .ConfigureAwait(false); if (token.IsCancellationRequested) { page = null; } } catch (OperationCanceledException) { } catch (Exception e) { Logger2.LogError(e, "Attempt to query site to determine forum adapter failed."); } return(page); }
/// <summary> /// Gets the list of threadmarks from an index page. /// Handle nested threadmarks. /// If the page doesn't have any threadmarks, return an empty list. /// Runs a filter (ThreadmarksFilter class) on any threadmark titles. /// Any that don't pass aren't included in the list. /// </summary> /// <param name="quest">The quest.</param> /// <param name="page">The index page of the threadmarks.</param> /// <returns>Returns a list of all unfiltered threadmark links.</returns> static IEnumerable <HtmlNode> GetThreadmarksListFromIndex(IQuest quest, HtmlDocument page) { try { HtmlNode content = GetPageContent(page, PageType.Threadmarks); HtmlNode?threadmarksDiv = content.GetDescendantWithClass("div", "threadmarks"); HtmlNode?listOfThreadmarks = null; HtmlNode?threadmarkList = threadmarksDiv?.GetDescendantWithClass("threadmarkList"); if (threadmarkList != null) { // We have a .threadmarkList node. This is either an ol itself, or it will contain a ThreadmarkCategory_# ol node. We want category 1. if (threadmarkList.Name == "ol") { if (threadmarkList.GetAttributeValue("class", "").Contains("ThreadmarkCategory")) { if (!threadmarkList.HasClass("ThreadmarkCategory_1")) { return(Enumerable.Empty <HtmlNode>()); } } listOfThreadmarks = threadmarkList; } else { listOfThreadmarks = threadmarkList.GetDescendantWithClass("ol", "ThreadmarkCategory_1"); } } else { // threadmarkList was null. There is no .threadmarkList node, so check for undecorated ul that contains .threadmarkItem list items. listOfThreadmarks = threadmarksDiv?.Descendants("ul").FirstOrDefault(e => e.Elements("li").Any(a => a.HasClass("threadmarkItem"))); } if (listOfThreadmarks != null) { Func <HtmlNode, bool> filterLambda = (n) => n != null && ((quest.UseCustomThreadmarkFilters && (quest.ThreadmarkFilter?.Match(n.InnerText) ?? false)) || (!quest.UseCustomThreadmarkFilters && DefaultThreadmarkFilter.Match(n.InnerText))); Func <HtmlNode, HtmlNode> nodeSelector = (n) => n.Element("a"); Func <HtmlNode, IEnumerable <HtmlNode> > childSelector = (i) => i.Element("ul")?.Elements("li") ?? new List <HtmlNode>(); var results = listOfThreadmarks.Elements("li").TraverseList(childSelector, nodeSelector, filterLambda); return(results); } } catch (ArgumentNullException e) { Logger2.LogError(e, "Failure when attempting to get the list of threadmarks from the index page. Null list somewhere?"); } return(Enumerable.Empty <HtmlNode>()); }
/// <summary> /// Get a completed post from the provided HTML list item node. /// </summary> /// <param name="li">List item node that contains the post.</param> /// <returns>Returns a post object with required information.</returns> private Post?GetPost(HtmlNode li, IQuest quest) { if (li == null) { throw new ArgumentNullException(nameof(li)); } string author; string id; string text; int number; // Author and ID are in the basic list item attributes author = ForumPostTextConverter.CleanupWebString(li.GetAttributeValue("data-author", "")); id = li.Id.Substring("post-".Length); if (AdvancedOptions.Instance.DebugMode) { author = $"{author}_{id}"; } // Get the primary content of the list item HtmlNode?primaryContent = li.GetChildWithClass("primaryContent"); // On one branch, we can get the post text HtmlNode?messageContent = primaryContent?.GetChildWithClass("messageContent"); HtmlNode?postBlock = messageContent?.Element("article")?.Element("blockquote"); // Predicate filtering out elements that we don't want to include List <string> excludedClasses = new List <string> { "bbCodeQuote", "messageTextEndMarker", "advbbcodebar_encadre", "advbbcodebar_article", "adv_tabs_wrapper", "adv_slider_wrapper" }; if (quest.IgnoreSpoilers) { excludedClasses.Add("bbCodeSpoilerContainer"); } var exclusions = ForumPostTextConverter.GetClassesExclusionPredicate(excludedClasses); // Get the full post text. text = ForumPostTextConverter.ExtractPostText(postBlock, exclusions, Host); // On another branch of the primary content, we can get the post number. HtmlNode?messageMeta = primaryContent?.GetChildWithClass("messageMeta"); // HTML parsing of the post was corrupted somehow. if (messageMeta == null) { return(null); } HtmlNode?publicControls = messageMeta.GetChildWithClass("publicControls"); HtmlNode?postNumber = publicControls?.GetChildWithClass("postNumber"); if (postNumber == null) { return(null); } string postNumberText = postNumber.InnerText; // Skip the leading # character. if (postNumberText.StartsWith("#", StringComparison.Ordinal)) { postNumberText = postNumberText.Substring(1); } number = int.Parse(postNumberText); Post?post; try { Origin origin = new Origin(author, id, number, Site, GetPermalinkForId(id)); post = new Post(origin, text); } catch (Exception e) { Logger2.LogError(e, $"Attempt to create new post failed. (Author:{author}, ID:{id}, Number:{number}, Quest:{quest.DisplayName})"); post = null; } return(post); }
/// <summary> /// Creates a regex based on the provided strings. /// Treats these strings as simple, comma-delimited option lists, /// with possible * globs. /// </summary> /// <param name="simpleString">The user-defined filter string.</param> /// <param name="injectString">The default, program-provided string to filter on.</param> /// <returns>Returns a regex constructed from the strings.</returns> private Regex CreateSimpleRegex(string simpleString, string?injectString) { if (string.IsNullOrEmpty(simpleString) && string.IsNullOrEmpty(injectString)) { return(EmptyRegex); } string safeGlobString = simpleString.RemoveUnsafeCharacters(); var splits = safeGlobString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); StringBuilder sb = new StringBuilder(); string bar = ""; // Convert comma-separated entries to |'d regex options. // If a segment contains special regex characters, escape those characters. // If a segment contains a *, treat it as a general glob: .* foreach (var split in splits) { string s = split.Trim(); if (!string.IsNullOrEmpty(s)) { s = escapeChars.Replace(s, @"\$1"); s = splat.Replace(s, @".*"); string preBound = ""; string postBound = ""; if (preWord.Match(s).Success) { preBound = @"\b"; } if (postWord.Match(s).Success) { postBound = @"\b"; } sb.Append(bar).Append(preBound).Append(s).Append(postBound); bar = "|"; } } // Add the default string value (if any) at the end. if (!string.IsNullOrEmpty(injectString)) { sb.Append(bar).Append(injectString); } try { return(new Regex($@"{sb.ToString()}", RegexOptions.IgnoreCase)); } catch (ArgumentException e) { Logger2.LogError(e, $"Failed to create regex using string: [{sb.ToString()}]"); } // If the attempt to create the regex to be returned failed, bail and // return a pure false regex. IsInverted = false; return(alwaysFalse); }