diff --git a/.gitignore b/.gitignore index 3e759b7..f306d43 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ +*.cs___jb_tmp___ diff --git a/README.md b/README.md index 38dcb32..2c848f2 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # Wiki.Net [![License](https://img.shields.io/github/license/Creepysin-Studios/Wiki.Net)](/LICENSE) [![Requirements Status](https://requires.io/github/Creepysin-Studios/Wiki.Net/requirements.svg?branch=Stable)](https://requires.io/github/Creepysin-Studios/Wiki.Net/requirements/?branch=Stable) [![NuGet](https://img.shields.io/nuget/v/Wiki.Net)](https://www.nuget.org/packages/Wiki.Net/) -[![Nuget](https://img.shields.io/nuget/dt/Wiki.Net)](https://www.nuget.org/packages/Wiki.Net/) [![Discord](https://img.shields.io/badge/Discord-Creepysin-7289da.svg?logo=discord)](https://discord.creepysin.com) Wiki.Net – An unofficial C# Wikipedia API ## Features -Searches Wikipedia (duh!) and returns: +Searches Wikipedia (duh!) and returns (per result): +* Title * Page ID -* Titles * Word Count * Size (bytes?) * Text Preview * URL of page +* Time of last edit ## Getting Started @@ -29,16 +29,18 @@ You can also download the binaries from the [releases](https://github.com/Creepy ### Example -```csharp +```c# string searchString = “Computer”; +WikiSearchSettings searchSettings = new WikiSearchSettings + {RequestId = "Request ID", ResultLimit = 5, ResultOffset = 2}; -WikiSearchResponse response = WikiSearcher.Search(searchString); +WikiSearchResponse response = WikiSearcher.Search(searchString, searchSettings); Console.WriteLine($"\nResults found ({searchString}):\n"); -for (int i = 0; i < response.SearchResults.Length; i++) +foreach (WikiSearchResult result in response.Query.SearchResults) { - WikiSearchResult result = response.SearchResults[i]; - Console.WriteLine($"\t{result.Title} ({result.WordCount} words, {result.Size} bytes, id {result.PageId}):\t{result.Preview}...\n\tAt {result.Url}\n\tLast edited at {result.LastEdited}\n"); + Console.WriteLine( + $"\t{result.Title} ({result.WordCount} words, {result.Size} bytes, id {result.PageId}):\t{result.Preview}...\n\tAt {result.Url}\n\tLast edited at {result.LastEdited}\n"); } Console.ReadLine(); @@ -46,20 +48,26 @@ Console.ReadLine(); **Output** ``` -Computer (12154 words) -A computer is a machine that can be instructed to carry out sequences of arithmetic or logical operations automatically via computer programming. Modern... -https://en.wikipedia.org/?curid=7878457 +Results found (Computer): -Computer science (7267 words) -Computer science (sometimes called computation science or computing science, but not to be confused with computational science or software engineering)... -https://en.wikipedia.org/?curid=5323 + Information technology (2836 words, 27146 bytes, id 36674345): Information technology (IT) is the use of computers to store, retrieve, transmit, and manipulate data, or information, often in the context of a business... + At https://en.wikipedia.org/?curid=36674345 + Last edited at 24/10/2019 11:53:39 AM -*More results* + Computer graphics (computer science) (1632 words, 18720 bytes, id 18567168): Computer graphics is a sub-field of Computer Science which studies methods for digitally synthesizing and manipulating visual content. Although the term... + At https://en.wikipedia.org/?curid=18567168 + Last edited at 17/09/2019 12:21:21 AM + + Computer hardware (2479 words, 22776 bytes, id 21808348): Computer hardware includes the physical, tangible parts or components of a computer, such as the cabinet, central processing unit, monitor, keyboard,... + At https://en.wikipedia.org/?curid=21808348 + Last edited at 16/10/2019 4:00:29 PM + + *More results* ``` ## Authors -**EternalClickbait** - *Initial work* - [EternalClickbait]( https://github.com/EternalClickbait) +**EternalClickbait** - *Initial work* - [EternalClickbait](https://github.com/EternalClickbait) ## License diff --git a/Wiki.Net.Example/Example.cs b/Wiki.Net.Example/Example.cs index af4652a..75ea59b 100644 --- a/Wiki.Net.Example/Example.cs +++ b/Wiki.Net.Example/Example.cs @@ -40,6 +40,8 @@ private static void Main() #region Loop until the user exits + WikiSearchSettings searchSettings = new WikiSearchSettings + {RequestId = "Request ID", ResultLimit = 5, ResultOffset = 2}; Request: //Get a search from the user, or exit string req = AskUserString("Enter a search query, 'exit' or 'quit' to quit"); @@ -53,7 +55,7 @@ private static void Main() } Console.Clear(); - PrintResults(req); + PrintResults(req, searchSettings); //Wait until the user presses enter to search again Console.WriteLine("Press any key to search again"); Console.ReadKey(true); @@ -62,17 +64,14 @@ private static void Main() #endregion } - private static void PrintResults(string searchString) + private static void PrintResults(string searchString, WikiSearchSettings searchSettings = null) { - WikiSearchResponse response = WikiSearcher.Search(searchString); + WikiSearchResponse response = WikiSearcher.Search(searchString, searchSettings); Console.WriteLine($"\nResults found ({searchString}):\n"); - for (int i = 0; i < response.SearchResults.Length; i++) - { - WikiSearchResult result = response.SearchResults[i]; + foreach (WikiSearchResult result in response.Query.SearchResults) Console.WriteLine( - $"\t{result.Title} ({result.WordCount} words, {result.Size} bytes, id {result.PageId}):\t{result.Preview}...\n\tAt {result.Url}\n\tLast edited at {result.LastEdited}\n"); - } + $"\t{result.Title} ({result.WordCount} words, {result.Size} bytes, id {result.PageId}):\t{result.Preview}...\n\tAt {result.Url} and {result.ConstantUrl}\n\tLast edited at {result.LastEdited}\n"); } private static string AskUserString(string message, bool clearConsole = true) diff --git a/Wiki.Net.sln.DotSettings b/Wiki.Net.sln.DotSettings index bfe8695..e98a52a 100644 --- a/Wiki.Net.sln.DotSettings +++ b/Wiki.Net.sln.DotSettings @@ -1,3 +1,4 @@  + True True True \ No newline at end of file diff --git a/Wiki.Net/Error.cs b/Wiki.Net/Error.cs new file mode 100644 index 0000000..ea8ce59 --- /dev/null +++ b/Wiki.Net/Error.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; + +namespace CreepysinStudios.WikiDotNet +{ + /// + /// A class that represents a Wikipedia API error + /// + // ReSharper disable once ClassCannotBeInstantiated + public sealed class Error + { + /// + /// What error code does this this error correspond to + /// + [JsonProperty("code")] public readonly string Code; + + /// + /// Any extra information the assist with debugging + /// + [JsonProperty("data")] public readonly string Data; + + /// + /// What Wikipedia module gave this error + /// + [JsonProperty("module")] public readonly string Module; + + /// + /// Information about this error + /// + [JsonProperty("*")] public readonly string Text; + + private Error() + { + } + } +} \ No newline at end of file diff --git a/Wiki.Net/Inheritance.md b/Wiki.Net/Inheritance.md new file mode 100644 index 0000000..f6edf83 --- /dev/null +++ b/Wiki.Net/Inheritance.md @@ -0,0 +1,16 @@ +# Class Inheritance + +### WikiSearchResponse +* #### Query + * ##### Search Results (Array) + * ###### Ns (Namespace?) + * ###### Last edited + * ###### Page ID + * ###### Preview + * ###### Size + * ###### Title + * ###### Word Count + * ###### Url + * ##### SearchInfo + * ###### TotalHits +* #### Request ID \ No newline at end of file diff --git a/Wiki.Net/SearchInfo.cs b/Wiki.Net/SearchInfo.cs new file mode 100644 index 0000000..98a04ba --- /dev/null +++ b/Wiki.Net/SearchInfo.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace CreepysinStudios.WikiDotNet +{ + /// + /// A class that contains information about a Wikipedia search. Currently only contains an int for the total number of + /// results. + /// + public class SearchInfo + { + /// + /// How many hits did the search return (in total, including those not shown) + /// + // ReSharper disable once StringLiteralTypo + [JsonProperty("totalhits")] public int TotalHits; + + private SearchInfo() + { + } + } +} \ No newline at end of file diff --git a/Wiki.Net/Warning.cs b/Wiki.Net/Warning.cs new file mode 100644 index 0000000..c6061a0 --- /dev/null +++ b/Wiki.Net/Warning.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; + +namespace CreepysinStudios.WikiDotNet +{ + /// + /// A class that represents a Wikipedia API warning. Often returned when invalid parameters/arguments are passed to the + /// Wikipedia API + /// + // ReSharper disable once ClassCannotBeInstantiated + public sealed class Warning + { + /// + /// What warning code does this this warning correspond to + /// + [JsonProperty("code")] public readonly string Code; + + /// + /// What Wikipedia module gave this warning + /// + [JsonProperty("module")] public readonly string Module; + + /// + /// Information about this warning + /// + [JsonProperty("*")] public readonly string Text; + + private Warning() + { + } + } +} \ No newline at end of file diff --git a/Wiki.Net/Wiki.Net.csproj b/Wiki.Net/Wiki.Net.csproj index 98eb1f1..f6960cb 100644 --- a/Wiki.Net/Wiki.Net.csproj +++ b/Wiki.Net/Wiki.Net.csproj @@ -24,4 +24,16 @@ + + + .gitignore + + + LICENSE + + + README.md + + + diff --git a/Wiki.Net/Wiki.Net.csproj.DotSettings b/Wiki.Net/Wiki.Net.csproj.DotSettings new file mode 100644 index 0000000..6ce52d4 --- /dev/null +++ b/Wiki.Net/Wiki.Net.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Wiki.Net/WikiSearchQuery.cs b/Wiki.Net/WikiSearchQuery.cs new file mode 100644 index 0000000..6679a6a --- /dev/null +++ b/Wiki.Net/WikiSearchQuery.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace CreepysinStudios.WikiDotNet +{ + /// + /// Contains an array of s and a + /// + // ReSharper disable once ClassCannotBeInstantiated + public sealed class WikiSearchQuery + { + /// + /// A read-only field that contains information such as the total amount of hits the search returned + /// + // ReSharper disable once StringLiteralTypo + [JsonProperty("searchinfo")] public readonly SearchInfo SearchInfo; + + /// + /// An array of results returned from the wikipedia servers + /// + [JsonProperty("search")] public readonly WikiSearchResult[] SearchResults; + + private WikiSearchQuery() + { + } + } +} \ No newline at end of file diff --git a/Wiki.Net/WikiSearchResponse.cs b/Wiki.Net/WikiSearchResponse.cs index dd2bc0a..debf933 100644 --- a/Wiki.Net/WikiSearchResponse.cs +++ b/Wiki.Net/WikiSearchResponse.cs @@ -1,44 +1,72 @@ #region using System; -using System.Net.Http; +using Newtonsoft.Json; #endregion namespace CreepysinStudios.WikiDotNet { /// - /// A class that contains an array of , returned from the Wikipedia servers + /// An object returned by the Wikipedia API that contains a and /// + //TODO: Add Error and warning class in case + // ReSharper disable once ClassCannotBeInstantiated public sealed class WikiSearchResponse { /// - /// The Json string from which the results were taken + /// Any errors returned with the request, or if there weren't any /// - public readonly string JsonResult; + [JsonProperty("errors")] public readonly Error[] Errors; /// - /// The response message from which the and are parsed + /// The Query that the search returned /// - public readonly HttpResponseMessage ResponseMessage; + [JsonProperty("query")] public readonly WikiSearchQuery Query; /// - /// An array of results returned from the wikipedia servers + /// The Request ID that was passed during the request /// - public readonly WikiSearchResult[] SearchResults; + // ReSharper disable once StringLiteralTypo + [JsonProperty("requestid")] public readonly string RequestId; /// - /// A constructor that creates a new + /// The Wikipedia server that this request was served by /// - /// The Json string used to parse the search results - /// The that was returned from the server - /// An array of parsed search results - internal WikiSearchResponse(string jsonResult, - HttpResponseMessage responseMessage, WikiSearchResult[] searchResults) + // ReSharper disable once StringLiteralTypo + [JsonProperty("servedby")] public readonly string ServedBy; + + /// + /// The time at which the Wikipedia server received the search request + /// + // ReSharper disable once StringLiteralTypo + [JsonProperty("curtimestamp")] public readonly DateTime Timestamp; + + /// + /// Any warnings returned with the request, or if there weren't any + /// + [JsonProperty("warnings")] public readonly Warning[] Warnings; + + private WikiSearchResponse() { - JsonResult = jsonResult ?? throw new ArgumentNullException(nameof(jsonResult)); - SearchResults = searchResults ?? throw new ArgumentNullException(nameof(searchResults)); - ResponseMessage = responseMessage ?? throw new ArgumentNullException(nameof(responseMessage)); + } + + /// + /// Was this request successful, or were there errors? + /// + public bool WasSuccessful + { + get + { + //If our errors and warnings arrays are null, we know this request was successful + if (Errors == null && Warnings == null) return true; + + //If our arrays aren't null and their length is not zero, return false + if (Warnings != null && Warnings.Length != 0) return false; + if (Errors != null && Errors.Length != 0) return false; + + return true; + } } } } \ No newline at end of file diff --git a/Wiki.Net/WikiSearchResult.cs b/Wiki.Net/WikiSearchResult.cs index f9e786f..6136846 100644 --- a/Wiki.Net/WikiSearchResult.cs +++ b/Wiki.Net/WikiSearchResult.cs @@ -10,8 +10,7 @@ namespace CreepysinStudios.WikiDotNet /// /// A single search result from a Wikipedia search /// - - //TODO: Add what categories the article falls into + // ReSharper disable once ClassCannotBeInstantiated public sealed class WikiSearchResult { /// @@ -19,6 +18,11 @@ public sealed class WikiSearchResult /// [JsonProperty("timestamp")] public readonly DateTime LastEdited; + /// + /// Unknown what this number refers to, likely refers to 'namespace' + /// + [JsonProperty("ns")] public readonly int Ns; + /// /// The numerical ID that corresponds internally (in Wikipedia's servers) to this page /// @@ -47,9 +51,20 @@ public sealed class WikiSearchResult // ReSharper disable once StringLiteralTypo [JsonProperty("wordcount")] public readonly int WordCount; + private WikiSearchResult() + { + } + + /// + /// A URL that can be used to access the article online. Created using the Page ID, and will point to the same article + /// even if the title changes + /// + public string ConstantUrl => $"https://en.wikipedia.org/?curid={PageId}"; + /// - /// The URL that can be used to access the article online. Created using the Page ID + /// A URL that can be used to access the article. If the page gets renamed or moved, this will likely break, and point + /// to a different or non-existent page /// - public string Url => $"https://en.wikipedia.org/?curid={PageId}"; + public string Url => $"https://en.wikipedia.org/wiki/{Title}"; } } \ No newline at end of file diff --git a/Wiki.Net/WikiSearchSettings.cs b/Wiki.Net/WikiSearchSettings.cs new file mode 100644 index 0000000..37370ae --- /dev/null +++ b/Wiki.Net/WikiSearchSettings.cs @@ -0,0 +1,65 @@ +#region + +using System; +using System.Collections.Generic; + +#endregion + +namespace CreepysinStudios.WikiDotNet +{ + /// + /// A struct containing settings for use when searching with . + /// + /// + public sealed class WikiSearchSettings + { + /// + /// What namespaces to search in. Default is {0} (default) + /// + // ReSharper disable once FieldCanBeMadeReadOnly.Global + public List Namespaces = null; + + /// + /// [Backing Field] How many results to return + /// + private int resultLimit = 10; + + /// + /// How many results to return + /// + /// Occurs when the given value is too high or low + public int ResultLimit + { + get => resultLimit; + set + { + const int min = 1; + const int max = 50; + if (value < min || value > max) + throw new ArgumentOutOfRangeException(nameof(value), + $"Value {value} is out of range. Valid range is {min}-{max}"); + resultLimit = value; + } + } + + /// + /// An amount to offset the search results by. Useful when scrolling through large groups of pages + /// + public int ResultOffset { get; set; } + + /// + /// A string that will be returned with the request results. Useful to distinguish multiple requests + /// + public string RequestId { get; set; } + + // ReSharper disable once CommentTypo + /// + /// Should we only find results that exactly match our search + /// Example: + /// 'Microsoft' results in 'Microsoft' + /// 'Microsof' results in 'no results' + /// + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global + public bool ExactMatch { get; set; } = false; + } +} \ No newline at end of file diff --git a/Wiki.Net/WikiSearcher.cs b/Wiki.Net/WikiSearcher.cs index d84713a..f7fdaa2 100644 --- a/Wiki.Net/WikiSearcher.cs +++ b/Wiki.Net/WikiSearcher.cs @@ -6,7 +6,6 @@ using System.Net.Http; using System.Text.RegularExpressions; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; #endregion @@ -17,12 +16,6 @@ namespace CreepysinStudios.WikiDotNet /// public static class WikiSearcher { - /// - /// The path we use to get results from - /// - private const string WikiGetPath = "https://en.wikipedia.org/w/api.php"; - - //Our HttpClient and handler that we use to request our information /// /// The that we use to request our information /// @@ -38,6 +31,11 @@ public static class WikiSearcher /// private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings(); + /// + /// The path we use to get results from + /// + private static string WikiGetPath => $"{(UseHttps ? "https://" : "http://")}en.wikipedia.org/w/api.php"; + /// /// An optional proxy to route HTTP requests through when searching /// @@ -48,34 +46,67 @@ public static IWebProxy Proxy set => Handler.Proxy = value; } + /// + /// If we should use HTTPS for web requests or HTTP + /// + // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global + public static bool UseHttps { get; set; } = true; + /// /// Searches Wikipedia using the given /// /// The string to search for + /// An optional set of settings to + /// /// A list of search results obtained from the Wikipedia API - public static WikiSearchResponse Search(string searchString) + public static WikiSearchResponse Search(string searchString, WikiSearchSettings searchSettings = null) { if (string.IsNullOrWhiteSpace(searchString)) throw new ArgumentNullException(nameof(searchString), "A search string must be provided"); //Encode our values to be passed to the server string url; - using (FormUrlEncodedContent content = new FormUrlEncodedContent(new[] + Dictionary args = new Dictionary { // ReSharper disable StringLiteralTypo - //Get results in Json - new KeyValuePair("format", "json"), //Query the Wiki API - new KeyValuePair("action", "query"), - //Give errors in plain text - new KeyValuePair("errorformat", "plaintext"), + ["action"] = "query", + ["list"] = "search", //Our search params - new KeyValuePair("list", "search"), - new KeyValuePair("srsearch", searchString) + ["srsearch"] = searchString, + //Get results in Json + ["format"] = "json", + //Give errors in plain text + ["errorformat"] = "plaintext" // ReSharper restore StringLiteralTypo - })) + }; + + if (searchSettings != null) + { + // ReSharper disable StringLiteralTypo + + //Limit our results, and offset if required + args.Add("srlimit", searchSettings.ResultLimit.ToString()); + args.Add("sroffset", searchSettings.ResultOffset.ToString()); + //If the namespaces list is null use "*" which means all of them + args.Add("srnamespace", + searchSettings.Namespaces == null ? "*" : string.Join('|', searchSettings.Namespaces)); + //If we should search for the exact string + args.Add("srwhat", searchSettings.ExactMatch ? "nearmatch" : "text"); + //Get which server we were served by + args.Add("servedby", "true"); + //Request the current timestamp be included + args.Add("curtimestamp", "true"); + if (searchSettings.RequestId != null) + args.Add("requestid", searchSettings.RequestId); + + // ReSharper restore StringLiteralTypo + } + + using (FormUrlEncodedContent content = new FormUrlEncodedContent(args)) { url = $"{WikiGetPath}?{content.ReadAsStringAsync().Result}"; } @@ -85,10 +116,8 @@ public static WikiSearchResponse Search(string searchString) string jsonResult = responseMessage.Content.ReadAsStringAsync().Result; jsonResult = StripTags(jsonResult); - WikiSearchResponse searchResponse = new WikiSearchResponse(jsonResult, responseMessage, - //We don't want to keep all of the extra information from our search, so we do some json magic to get the inner property - JsonConvert.DeserializeObject(jsonResult, JsonSerializerSettings).GetValue("query") - .ToObject().GetValue("search").ToObject()); + WikiSearchResponse searchResponse = + JsonConvert.DeserializeObject(jsonResult, JsonSerializerSettings); return searchResponse; } @@ -102,7 +131,7 @@ private static string StripTags(string source) { //We need to replace any quotes before they get processed by the HTML decoder, or they don't get escaped and deal havoc with the Json string unquoted = source.Replace(""", "\\\""); - //Decode html entity codes like `"` into their unicode counterparts (e.g. `"` => `"`) + //Decode html entity codes like `<` into their unicode counterparts (e.g. `<` => `<`) string decoded = WebUtility.HtmlDecode(unquoted); //Remove html formatting tags like ,
etc. return Regex.Replace(decoded, "<.*?>", string.Empty);