├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── generic-build.yml │ └── nuget-release.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE.txt ├── README.md └── src ├── Wikipedia.Examples ├── Program.cs └── Wikipedia.Examples.csproj ├── Wikipedia.Tests ├── WikiMediaRequestTests.cs ├── WikiSearchRequestTests.cs └── Wikipedia.Tests.csproj ├── Wikipedia.sln └── Wikipedia ├── Enums ├── WikiErrorFormat.cs ├── WikiInfo.cs ├── WikiLanguage.cs ├── WikiNamespace.cs ├── WikiProperty.cs ├── WikiQueryProfile.cs ├── WikiSortOrder.cs └── WikiWhat.cs ├── Extensions └── EnumExtensions.cs ├── Internal ├── LowerCasePolicy.cs ├── StringValueAttribute.cs └── UrlHelper.cs ├── Objects ├── Continuation.cs ├── Error.cs ├── ModuleError.cs ├── QueryResult.cs ├── SearchInfo.cs └── SearchResult.cs ├── WikiMediaRequest.cs ├── WikiSearchRequest.cs ├── WikiSearchResponse.cs ├── Wikipedia.csproj └── WikipediaClient.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] 3 | indent_style = space 4 | indent_size = 2 5 | tab_width = 4 6 | 7 | [*.{c,c++,cc,cginc,compute,cp,cpp,cu,cuh,cxx,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,mpp,mq4,mq5,mqh,proto,tpp,usf,ush}] 8 | indent_style = space 9 | indent_size = 4 10 | tab_width = 4 11 | 12 | [*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] 13 | indent_style = space 14 | indent_size = 4 15 | tab_width = 4 16 | 17 | [*.{json,resjson}] 18 | indent_style = space 19 | indent_size = 2 20 | tab_width = 2 21 | 22 | [*] 23 | 24 | # Microsoft .NET properties 25 | csharp_new_line_before_members_in_object_initializers = false 26 | csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion 27 | csharp_space_after_cast = true 28 | csharp_style_var_elsewhere = false:suggestion 29 | csharp_style_var_for_built_in_types = false:suggestion 30 | csharp_style_var_when_type_is_apparent = false:suggestion 31 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none 32 | dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none 33 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none 34 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 35 | dotnet_style_predefined_type_for_member_access = true:suggestion 36 | dotnet_style_qualification_for_event = false:suggestion 37 | dotnet_style_qualification_for_field = false:suggestion 38 | dotnet_style_qualification_for_method = false:suggestion 39 | dotnet_style_qualification_for_property = false:suggestion 40 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 41 | dotnet_style_object_initializer = false 42 | 43 | # ReSharper properties 44 | resharper_align_linq_query = true 45 | resharper_align_multiline_array_and_object_initializer = true 46 | resharper_align_multiline_binary_patterns = true 47 | resharper_align_multiline_calls_chain = true 48 | resharper_align_multiline_expression = true 49 | resharper_align_multiline_extends_list = true 50 | resharper_align_multiline_property_pattern = true 51 | resharper_align_multline_type_parameter_constrains = true 52 | resharper_align_multline_type_parameter_list = true 53 | resharper_align_tuple_components = true 54 | resharper_blank_lines_around_single_line_type = 0 55 | resharper_blank_lines_before_single_line_comment = 1 56 | resharper_continuous_indent_multiplier = 2 57 | resharper_csharp_align_multiline_parameter = true 58 | resharper_csharp_align_multiple_declaration = true 59 | resharper_csharp_blank_lines_inside_region = 0 60 | resharper_csharp_empty_block_style = together_same_line 61 | resharper_csharp_outdent_dots = true 62 | resharper_csharp_stick_comment = false 63 | resharper_csharp_use_indent_from_vs = false 64 | resharper_csharp_wrap_lines = false 65 | resharper_indent_anonymous_method_block = true 66 | resharper_indent_preprocessor_region = no_indent 67 | resharper_keep_existing_embedded_arrangement = false 68 | resharper_place_simple_embedded_statement_on_same_line = false 69 | resharper_space_within_single_line_array_initializer_braces = false 70 | resharper_use_continuous_indent_inside_initializer_braces = false 71 | resharper_wrap_before_eq = true 72 | 73 | # ReSharper inspection severities 74 | resharper_arrange_object_creation_when_type_evident_highlighting = none 75 | resharper_arrange_redundant_parentheses_highlighting = hint 76 | resharper_arrange_this_qualifier_highlighting = hint 77 | resharper_arrange_type_member_modifiers_highlighting = hint 78 | resharper_arrange_type_modifiers_highlighting = hint 79 | resharper_built_in_type_reference_style_for_member_access_highlighting = hint 80 | resharper_built_in_type_reference_style_highlighting = hint 81 | resharper_redundant_base_qualifier_highlighting = warning 82 | resharper_suggest_var_or_type_built_in_types_highlighting = hint 83 | resharper_suggest_var_or_type_elsewhere_highlighting = hint 84 | resharper_suggest_var_or_type_simple_types_highlighting = hint 85 | csharp_space_around_declaration_statements = false 86 | resharper_use_object_or_collection_initializer_highlighting = false 87 | 88 | # Analyzers 89 | roslynator_accessibility_modifiers = explicit 90 | roslynator_enum_has_flag_style = method 91 | roslynator_object_creation_type_style = explicit 92 | roslynator_use_var_instead_of_implicit_object_creation = false 93 | csharp_style_namespace_declarations = file_scoped:error 94 | dotnet_diagnostic.xunit2003.severity = error 95 | dotnet_diagnostic.xunit2004.severity = error 96 | dotnet_diagnostic.ca1018.severity = error 97 | dotnet_diagnostic.ca1041.severity = error 98 | dotnet_diagnostic.ca1056.severity = silent 99 | dotnet_diagnostic.ca1055.severity = silent 100 | dotnet_diagnostic.ca1054.severity = silent 101 | dotnet_diagnostic.ca1200.severity = warning 102 | dotnet_diagnostic.ca1309.severity = suggestion 103 | dotnet_diagnostic.ca1401.severity = warning 104 | dotnet_diagnostic.ca1507.severity = warning 105 | dotnet_diagnostic.ca1821.severity = error 106 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Linux scripts 2 | *.sh text eol=lf 3 | *.py text diff=python eol=lf 4 | 5 | # Linux configs 6 | *.conf text eol=lf 7 | *.yml text eol=lf 8 | 9 | # Windows scripts 10 | *.ps1 text eol=crlf 11 | *.cmd text eol=crlf 12 | *.bat text eol=crlf 13 | 14 | # Source code files 15 | *.cs text diff=csharp 16 | *.cshtml text 17 | *.json text 18 | *.xml text 19 | *.html text diff=html 20 | *.css text diff=css 21 | *.scss text diff=css 22 | *.js text 23 | *.csproj text 24 | *.targets text ident 25 | *.sln text 26 | 27 | # Images 28 | *.svg text 29 | 30 | # Text files 31 | *.txt text 32 | *.md text 33 | *.markdowm text 34 | 35 | ############################### 36 | # Git Large File System (LFS) # 37 | ############################### 38 | 39 | # Archives 40 | *.7z filter=lfs diff=lfs merge=lfs -text 41 | *.rar filter=lfs diff=lfs merge=lfs -text 42 | *.br filter=lfs diff=lfs merge=lfs -text 43 | *.gz filter=lfs diff=lfs merge=lfs -text 44 | *.tar filter=lfs diff=lfs merge=lfs -text 45 | *.zip filter=lfs diff=lfs merge=lfs -text 46 | 47 | # Documents 48 | *.pdf filter=lfs diff=lfs merge=lfs -text 49 | *.doc filter=lfs diff=lfs merge=lfs -text 50 | *.docx filter=lfs diff=lfs merge=lfs -text 51 | 52 | # Images 53 | *.gif filter=lfs diff=lfs merge=lfs -text 54 | *.ico filter=lfs diff=lfs merge=lfs -text 55 | *.jpg filter=lfs diff=lfs merge=lfs -text 56 | *.png filter=lfs diff=lfs merge=lfs -text 57 | *.psd filter=lfs diff=lfs merge=lfs -text 58 | *.webp filter=lfs diff=lfs merge=lfs -text 59 | *.bmp filter=lfs diff=lfs merge=lfs -text 60 | 61 | # Fonts 62 | *.woff2 filter=lfs diff=lfs merge=lfs -text 63 | 64 | # Other 65 | *.exe filter=lfs diff=lfs merge=lfs -text -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Genbox] 2 | custom: http://paypal.me/IanQvist 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | * [ ] I hereby verify that I am a sponsor. 11 | Sponsorship is required before you can submit bug reports. See https://github.com/sponsors/Genbox 12 | 13 | **Describe the bug** 14 | What is the bug about? 15 | 16 | **How to reproduce?** 17 | Describe steps to reproduce the bug. Please include code to reproduce if possible. 18 | 19 | **Expected behavior** 20 | Describe what you expected to happen. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | * [ ] I hereby verify that I am a sponsor. 11 | Sponsorship is required before you can submit feature requests. See https://github.com/sponsors/Genbox 12 | 13 | **Describe the feature** 14 | Provide a short description of the feature. Why would you like the feature? 15 | If the feature makes changes to an existing or new API please provide an example. 16 | -------------------------------------------------------------------------------- /.github/workflows/generic-build.yml: -------------------------------------------------------------------------------- 1 | name: Generic build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Setup .NET Core 6.0 12 | uses: actions/setup-dotnet@v1 13 | with: 14 | dotnet-version: '6.0.x' 15 | - name: Build free 16 | run: dotnet build -c Release src/Wikipedia.sln -------------------------------------------------------------------------------- /.github/workflows/nuget-release.yml: -------------------------------------------------------------------------------- 1 | name: Nuget release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+\.[0-9]+\.[0-9]+' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup .NET Core 6.0 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: '6.0.x' 18 | - name: Pack release 19 | run: dotnet pack -c Release src/Wikipedia.sln -o Temp 20 | - name: Upload to nuget 21 | run: dotnet nuget push --skip-duplicate -k ${{secrets.NUGET_KEY}} -s https://api.nuget.org/v3/index.json Temp/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | [Bb]in/ 3 | [Oo]bj/ 4 | [Ll]og/ 5 | 6 | # NuGet Packages 7 | *.nupkg 8 | *.snupkg 9 | 10 | # Others 11 | *.pfx 12 | *.publishsettings 13 | 14 | # SQL Server files 15 | *.mdf 16 | *.ldf 17 | *.ndf 18 | 19 | # Visual Studio cache folder 20 | .vs/ -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ian Qvist 6 | Copyright 2022, by Ian Qvist. All rights reserved. 7 | A simple .NET based search client for Wikipedia 8 | 9 | 10 | 11 | 12 | 13 | 2 14 | 500 15 | 16 | 17 | Genbox.$(MSBuildProjectName) 18 | Genbox.$(MSBuildProjectName) 19 | 20 | 21 | latest 22 | enable 23 | 24 | 25 | true 26 | false 27 | 28 | 29 | portable 30 | 31 | 32 | Git 33 | https://github.com/Genbox/$(MSBuildProjectName) 34 | MIT 35 | 36 | 37 | true 38 | true 39 | true 40 | snupkg 41 | 42 | 43 | 0 44 | false 45 | false 46 | false 47 | none 48 | 49 | 50 | none 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | true 73 | true 74 | true 75 | 5 76 | Default 77 | latest 78 | 79 | 80 | 81 | false 82 | true 83 | 84 | 85 | 86 | true 87 | false 88 | 89 | 90 | 91 | 92 | 93 | $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).0 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ian Qvist 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wikipedia - An implementation of the full text search API of Wikipedia 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/Genbox.Wikipedia.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/Genbox.Wikipedia/) 4 | [![Build](https://img.shields.io/github/workflow/status/Genbox/Wikipedia/Generic%20build?label=Build)](https://github.com/Genbox/Wikipedia/actions) 5 | [![Release](https://img.shields.io/github/workflow/status/Genbox/Wikipedia/Nuget%20release?label=Release)](https://github.com/Genbox/Wikipedia/actions) 6 | [![License](https://img.shields.io/github/license/Genbox/Wikipedia)](https://github.com/Genbox/Wikipedia/blob/master/LICENSE.txt) 7 | 8 | ### Features 9 | 10 | * Support for all 283 languages on Wikipedia 11 | * Support for all search parameters as of MediaWiki v1.24 12 | 13 | ### Example 14 | 15 | Here is the simplest way of getting data from Wikipedia: 16 | 17 | ```csharp 18 | static async Task Main() 19 | { 20 | using WikipediaClient client = new WikipediaClient(); 21 | 22 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 23 | req.Limit = 5; //We would like 5 results 24 | 25 | WikiSearchResponse resp = await client.SearchAsync(req); 26 | 27 | foreach (SearchResult s in resp.QueryResult.SearchResults) 28 | { 29 | Console.WriteLine($" - {s.Title}"); 30 | } 31 | } 32 | ``` 33 | 34 | Output: 35 | ``` 36 | - Albert Einstein 37 | - Hans Albert Einstein 38 | - Einstein family 39 | - Albert Brooks 40 | - Albert Einstein College of Medicine 41 | ``` -------------------------------------------------------------------------------- /src/Wikipedia.Examples/Program.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Enums; 2 | using Genbox.Wikipedia.Objects; 3 | 4 | namespace Genbox.Wikipedia.Examples; 5 | 6 | internal static class Program 7 | { 8 | private static async Task Main() 9 | { 10 | //Default language is English 11 | using WikipediaClient client = new WikipediaClient(); 12 | 13 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 14 | req.Limit = 5; //We would like 5 results 15 | req.WhatToSearch = WikiWhat.Text; //We would like to search inside the articles 16 | 17 | WikiSearchResponse resp = await client.SearchAsync(req).ConfigureAwait(false); 18 | 19 | Console.WriteLine($"Searching for {req.Query}"); 20 | Console.WriteLine(); 21 | Console.WriteLine($"Found {resp.QueryResult.SearchResults.Count} English results:"); 22 | 23 | foreach (SearchResult s in resp.QueryResult.SearchResults) 24 | { 25 | Console.WriteLine($" - {s.Title}"); 26 | } 27 | 28 | Console.WriteLine(); 29 | Console.WriteLine(); 30 | 31 | //We change the language to Spanish 32 | req.WikiLanguage = WikiLanguage.Spanish; 33 | 34 | resp = await client.SearchAsync(req).ConfigureAwait(false); 35 | 36 | Console.WriteLine($"Found {resp.QueryResult.SearchResults.Count} Spanish results:"); 37 | 38 | foreach (SearchResult s in resp.QueryResult.SearchResults) 39 | { 40 | Console.WriteLine($" - {s.Title}"); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Wikipedia.Examples/Wikipedia.Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Wikipedia.Tests/WikiMediaRequestTests.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Enums; 2 | using Xunit; 3 | 4 | namespace Genbox.Wikipedia.Tests; 5 | 6 | public sealed class WikiMediaRequestTests : IDisposable 7 | { 8 | private readonly WikipediaClient _client; 9 | 10 | public WikiMediaRequestTests() 11 | { 12 | _client = new WikipediaClient(); 13 | } 14 | 15 | [Fact] 16 | public async Task AssertTest() 17 | { 18 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 19 | req.Assert = "anon"; 20 | 21 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 22 | Assert.Null(resp.Error); //We expect there to be no error since we are not logged in 23 | 24 | req.Assert = "user"; 25 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 26 | Assert.NotNull(resp.Error); //We expect an error because we are not logged in 27 | Assert.Equal("assertuserfailed", resp.Error.Code); 28 | Assert.Equal("See https://en.wikipedia.org/w/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at <https://lists.wikimedia.org/postorius/lists/mediawiki-api-announce.lists.wikimedia.org/> for notice of API deprecations and breaking changes.", resp.Error.DocumentReference); 29 | Assert.Equal("You are no longer logged in, so the action could not be completed.", resp.Error.Info); 30 | } 31 | 32 | [Fact] 33 | public async Task AssertUserTest() 34 | { 35 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 36 | req.AssertUser = "someone"; 37 | 38 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 39 | Assert.Equal("assertnameduserfailed", resp.Error.Code); 40 | Assert.Equal("See https://en.wikipedia.org/w/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at <https://lists.wikimedia.org/postorius/lists/mediawiki-api-announce.lists.wikimedia.org/> for notice of API deprecations and breaking changes.", resp.Error.DocumentReference); 41 | Assert.Equal("You are no longer logged in as \"Someone\", so the action could not be completed.", resp.Error.Info); 42 | } 43 | 44 | [Fact] 45 | public async Task IncludeServedByTest() 46 | { 47 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 48 | req.IncludeServedBy = true; 49 | 50 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 51 | Assert.NotNull(resp.ServedBy); 52 | } 53 | 54 | [Fact] 55 | public async Task IncludeCurrentTimestampTest() 56 | { 57 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 58 | req.IncludeCurrentTimestamp = true; 59 | 60 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 61 | Assert.Equal(DateTime.UtcNow, resp.CurrentTimestamp.DateTime, TimeSpan.FromMinutes(1)); 62 | } 63 | 64 | [Fact] 65 | public async Task IncludeLanguageUsedTest() 66 | { 67 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 68 | req.IncludeLanguageUsed = true; 69 | 70 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 71 | Assert.Equal("en", resp.ErrorLanguage); 72 | Assert.Equal("en", resp.Language); 73 | } 74 | 75 | [Fact] 76 | public async Task RequestIdTest() 77 | { 78 | Guid id = Guid.NewGuid(); 79 | 80 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 81 | req.RequestId = id.ToString(); 82 | 83 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 84 | Assert.Equal(id.ToString(), resp.RequestId); 85 | } 86 | 87 | [Fact] 88 | public async Task LanguageToUseAndVariantTest() 89 | { 90 | //Go to https://www.mediawiki.org/w/api.php?action=query&meta=siteinfo&siprop=languages&formatversion=2 to see languages 91 | 92 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 93 | req.LanguageToUse = "es"; 94 | req.LanguageVariant = "es-419"; 95 | req.IncludeLanguageUsed = true; 96 | 97 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 98 | Assert.Equal("es", resp.Language); 99 | } 100 | 101 | [Fact(Skip = "Unable to create a working test case")] 102 | public async Task ErrorLanguageToUseTest() 103 | { 104 | //Go to https://www.mediawiki.org/w/api.php?action=query&meta=siteinfo&siprop=languages&formatversion=2 to see languages 105 | 106 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 107 | req.WikiLanguage = WikiLanguage.Spanish; 108 | req.IncludeLanguageUsed = true; 109 | 110 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 111 | Assert.Equal("es", resp.ErrorLanguage); 112 | } 113 | 114 | [Fact] 115 | public async Task ErrorFormatTest() 116 | { 117 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 118 | req.AssertUser = "whatever"; //We use AssertUser to provoke an error 119 | req.ErrorFormat = WikiErrorFormat.Bc; 120 | 121 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 122 | Assert.Equal("You are no longer logged in as \"Whatever\", so the action could not be completed.", resp.Error.Info); 123 | 124 | req.ErrorFormat = WikiErrorFormat.Html; 125 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 126 | Assert.Equal("assertnameduserfailed", resp.ModuleErrors[0].Code); 127 | Assert.Equal("main", resp.ModuleErrors[0].Module); 128 | Assert.Equal("You are no longer logged in as \"Whatever\", so the action could not be completed.", resp.ModuleErrors[0].Html); 129 | 130 | req.ErrorFormat = WikiErrorFormat.Plaintext; 131 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 132 | Assert.Equal("assertnameduserfailed", resp.ModuleErrors[0].Code); 133 | Assert.Equal("main", resp.ModuleErrors[0].Module); 134 | Assert.Equal("You are no longer logged in as \"Whatever\", so the action could not be completed.", resp.ModuleErrors[0].Text); 135 | 136 | req.ErrorFormat = WikiErrorFormat.None; 137 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 138 | Assert.Equal("assertnameduserfailed", resp.ModuleErrors[0].Code); 139 | Assert.Equal("main", resp.ModuleErrors[0].Module); 140 | Assert.Null(resp.ModuleErrors[0].Text); 141 | Assert.Null(resp.ModuleErrors[0].Html); 142 | 143 | req.ErrorFormat = WikiErrorFormat.Raw; 144 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 145 | Assert.Equal("assertnameduserfailed", resp.ModuleErrors[0].Code); 146 | Assert.Equal("main", resp.ModuleErrors[0].Module); 147 | Assert.Equal("apierror-assertnameduserfailed", resp.ModuleErrors[0].Key); 148 | Assert.Equal("Whatever", resp.ModuleErrors[0].Params[0]); 149 | 150 | req.ErrorFormat = WikiErrorFormat.WikiText; 151 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 152 | Assert.Equal("assertnameduserfailed", resp.ModuleErrors[0].Code); 153 | Assert.Equal("main", resp.ModuleErrors[0].Module); 154 | Assert.Equal("You are no longer logged in as \"Whatever\", so the action could not be completed.", resp.ModuleErrors[0].Text); 155 | } 156 | 157 | public void Dispose() 158 | { 159 | _client.Dispose(); 160 | } 161 | } -------------------------------------------------------------------------------- /src/Wikipedia.Tests/WikiSearchRequestTests.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Enums; 2 | using Genbox.Wikipedia.Extensions; 3 | using Genbox.Wikipedia.Objects; 4 | using Xunit; 5 | 6 | namespace Genbox.Wikipedia.Tests; 7 | 8 | public sealed class WikiSearchRequestTests : IDisposable 9 | { 10 | private readonly WikipediaClient _client; 11 | 12 | public WikiSearchRequestTests() 13 | { 14 | _client = new WikipediaClient(); 15 | } 16 | 17 | [Fact] 18 | public async Task NamespacesToIncludeTest() 19 | { 20 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 21 | req.NamespacesToInclude = WikiNamespace.Category | WikiNamespace.CategoryTalk; 22 | req.Limit = 100; 23 | 24 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 25 | 26 | //Ideally we should have results from both categories 27 | Assert.Contains(resp.QueryResult.SearchResults, x => x.Namespace.ToString() == WikiNamespace.Category.GetStringValue()); 28 | Assert.Contains(resp.QueryResult.SearchResults, x => x.Namespace.ToString() == WikiNamespace.CategoryTalk.GetStringValue()); 29 | } 30 | 31 | [Fact] 32 | public async Task LimitAndOffsetTest() 33 | { 34 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 35 | req.Limit = 1; 36 | 37 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 38 | SearchResult? item = Assert.Single(resp.QueryResult.SearchResults); 39 | 40 | req.Offset = 1; 41 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 42 | 43 | SearchResult? otherItem = Assert.Single(resp.QueryResult.SearchResults); 44 | 45 | Assert.NotEqual(item, otherItem); 46 | } 47 | 48 | [Fact] 49 | public async Task QueryIndependentProfileTest() 50 | { 51 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 52 | req.QueryIndependentProfile = WikiQueryProfile.Classic; 53 | 54 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 55 | Assert.NotNull(resp.QueryResult); 56 | } 57 | 58 | [Fact] 59 | public async Task WhatToSearchTest() 60 | { 61 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 62 | req.WhatToSearch = WikiWhat.Text; 63 | 64 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 65 | Assert.NotNull(resp.QueryResult); 66 | } 67 | 68 | [Fact] 69 | public async Task InfoToIncludeTest() 70 | { 71 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstei"); 72 | req.InfoToInclude = WikiInfo.TotalHits | WikiInfo.Suggestion; 73 | 74 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 75 | Assert.Equal("albert einstein", resp.QueryResult.SearchInfo.Suggestion); 76 | Assert.Equal("albert einstein", resp.QueryResult.SearchInfo.SuggestionSnippet); 77 | Assert.NotEqual(0, resp.QueryResult.SearchInfo.TotalHits); 78 | } 79 | 80 | [Fact] 81 | public async Task PropertiesToIncludeTest() 82 | { 83 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 84 | req.PropertiesToInclude = WikiProperty.All; 85 | 86 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 87 | 88 | SearchResult s = resp.QueryResult.SearchResults[0]; 89 | 90 | Assert.NotNull(s.CategorySnippet); 91 | Assert.NotNull(s.IsFileMatch); 92 | Assert.NotEqual(0, s.Size); 93 | Assert.NotEqual(0, s.PageId); 94 | Assert.NotNull(s.Snippet); 95 | Assert.NotNull(s.TimeStamp); 96 | Assert.NotNull(s.Title); 97 | Assert.NotNull(s.TitleSnippet); 98 | Assert.NotEqual(0, s.WordCount); 99 | 100 | req = new WikiSearchRequest("Albert Einstein"); 101 | req.PropertiesToInclude = WikiProperty.Size; 102 | 103 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 104 | 105 | s = resp.QueryResult.SearchResults[0]; 106 | Assert.NotEqual(0, s.Size); 107 | Assert.NotNull(s.Title); 108 | Assert.Null(s.CategorySnippet); 109 | Assert.Null(s.IsFileMatch); 110 | Assert.Null(s.Snippet); 111 | Assert.Null(s.TimeStamp); 112 | Assert.Null(s.TitleSnippet); 113 | Assert.Null(s.WordCount); 114 | } 115 | 116 | [Fact] 117 | public async Task IncludeInterWikiResultsTest() 118 | { 119 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstei"); 120 | req.IncludeInterWikiResults = true; 121 | 122 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 123 | Assert.NotNull(resp.QueryResult); 124 | } 125 | 126 | [Fact] 127 | public async Task SortOrderTest() 128 | { 129 | WikiSearchRequest req = new WikiSearchRequest("Albert Einstein"); 130 | req.SortOrder = WikiSortOrder.CreateTimestampAsc; 131 | 132 | WikiSearchResponse resp = await _client.SearchAsync(req).ConfigureAwait(false); 133 | SearchResult item = resp.QueryResult.SearchResults[0]; 134 | 135 | req.SortOrder = WikiSortOrder.CreateTimestampDesc; 136 | resp = await _client.SearchAsync(req).ConfigureAwait(false); 137 | 138 | SearchResult otherItem = resp.QueryResult.SearchResults[0]; 139 | 140 | Assert.NotEqual(item, otherItem); 141 | } 142 | 143 | public void Dispose() 144 | { 145 | _client.Dispose(); 146 | } 147 | } -------------------------------------------------------------------------------- /src/Wikipedia.Tests/Wikipedia.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Wikipedia.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Items", "_Items", "{7FC54FC1-575A-4BD7-B93B-D30E2F3AA6DC}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\.editorconfig = ..\.editorconfig 9 | ..\.gitattributes = ..\.gitattributes 10 | ..\.gitignore = ..\.gitignore 11 | ..\Directory.Build.props = ..\Directory.Build.props 12 | ..\LICENSE.txt = ..\LICENSE.txt 13 | ..\README.md = ..\README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wikipedia", "Wikipedia\Wikipedia.csproj", "{5DAAD97E-41B7-4F9A-9CAE-1F44CB61E449}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wikipedia.Examples", "Wikipedia.Examples\Wikipedia.Examples.csproj", "{A8805B03-BFC9-4AD6-89C8-B52DF8FCE337}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wikipedia.Tests", "Wikipedia.Tests\Wikipedia.Tests.csproj", "{6CF44102-30B8-4B6C-86AD-CFA02FEE63A5}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {5DAAD97E-41B7-4F9A-9CAE-1F44CB61E449}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {5DAAD97E-41B7-4F9A-9CAE-1F44CB61E449}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {5DAAD97E-41B7-4F9A-9CAE-1F44CB61E449}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {5DAAD97E-41B7-4F9A-9CAE-1F44CB61E449}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {A8805B03-BFC9-4AD6-89C8-B52DF8FCE337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {A8805B03-BFC9-4AD6-89C8-B52DF8FCE337}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {A8805B03-BFC9-4AD6-89C8-B52DF8FCE337}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {A8805B03-BFC9-4AD6-89C8-B52DF8FCE337}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {6CF44102-30B8-4B6C-86AD-CFA02FEE63A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {6CF44102-30B8-4B6C-86AD-CFA02FEE63A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {6CF44102-30B8-4B6C-86AD-CFA02FEE63A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {6CF44102-30B8-4B6C-86AD-CFA02FEE63A5}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(ExtensibilityGlobals) = postSolution 45 | SolutionGuid = {5AAF35F0-620F-4CF1-981B-9FC7B6652049} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiErrorFormat.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | public enum WikiErrorFormat 6 | { 7 | NotSet = 0, 8 | 9 | /// Format used prior to MediaWiki 1.29. and 10 | /// are ignored. 11 | [StringValue("bc")] Bc, 12 | 13 | /// HTML 14 | [StringValue("html")] Html, 15 | 16 | /// No text output, only the error codes. 17 | [StringValue("none")] None, 18 | 19 | /// Wikitext with HTML tags removed and entities replaced. 20 | [StringValue("plaintext")] Plaintext, 21 | 22 | /// Message key and parameters. 23 | [StringValue("raw")] Raw, 24 | 25 | /// Unparsed wikitext. 26 | [StringValue("wikitext")] WikiText 27 | } -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiInfo.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | [Flags] 6 | public enum WikiInfo 7 | { 8 | NotSet = 0, 9 | 10 | /// The number of search results 11 | [StringValue("totalhits")] TotalHits = 1 << 0, 12 | 13 | /// A suggestion that might fit better than what you searched for. 14 | [StringValue("suggestion")] Suggestion = 1 << 1, 15 | 16 | [StringValue("rewrittenquery")] RewrittenQuery = 1 << 2 17 | } -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiLanguage.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | /// All the supported languages from Wikipedia. Source: http://meta.wikimedia.org/wiki/List_of_Wikipedias 6 | public enum WikiLanguage 7 | { 8 | NotSet = 0, 9 | [StringValue("en")] English, 10 | [StringValue("de")] German, 11 | [StringValue("fr")] French, 12 | [StringValue("nl")] Dutch, 13 | [StringValue("it")] Italian, 14 | [StringValue("pl")] Polish, 15 | [StringValue("es")] Spanish, 16 | [StringValue("ru")] Russian, 17 | [StringValue("ja")] Japanese, 18 | [StringValue("pt")] Portuguese, 19 | [StringValue("zh")] Chinese, 20 | [StringValue("sv")] Swedish, 21 | [StringValue("vi")] Vietnamese, 22 | [StringValue("uk")] Ukrainian, 23 | [StringValue("ca")] Catalan, 24 | [StringValue("no")] NorwegianBokmål, 25 | [StringValue("fi")] Finnish, 26 | [StringValue("cs")] Czech, 27 | [StringValue("hu")] Hungarian, 28 | [StringValue("fa")] Persian, 29 | [StringValue("ko")] Korean, 30 | [StringValue("id")] Indonesian, 31 | [StringValue("tr")] Turkish, 32 | [StringValue("ar")] Arabic, 33 | [StringValue("ro")] Romanian, 34 | [StringValue("sk")] Slovak, 35 | [StringValue("eo")] Esperanto, 36 | [StringValue("da")] Danish, 37 | [StringValue("sr")] Serbian, 38 | [StringValue("lt")] Lithuanian, 39 | [StringValue("ms")] Malay, 40 | [StringValue("kk")] Kazakh, 41 | [StringValue("he")] Hebrew, 42 | [StringValue("eu")] Basque, 43 | [StringValue("bg")] Bulgarian, 44 | [StringValue("sl")] Slovenian, 45 | [StringValue("vo")] Volapük, 46 | [StringValue("hr")] Croatian, 47 | [StringValue("war")] WarayWaray, 48 | [StringValue("hi")] Hindi, 49 | [StringValue("et")] Estonian, 50 | [StringValue("az")] Azerbaijani, 51 | [StringValue("gl")] Galician, 52 | [StringValue("nn")] NorwegianNynorsk, 53 | [StringValue("simple")] SimpleEnglish, 54 | [StringValue("la")] Latin, 55 | [StringValue("el")] Greek, 56 | [StringValue("th")] Thai, 57 | [StringValue("new")] NewarNepalBhasa, 58 | [StringValue("sh")] SerboCroatian, 59 | [StringValue("roa-rup")] Aromanian, 60 | [StringValue("oc")] Occitan, 61 | [StringValue("ka")] Georgian, 62 | [StringValue("mk")] Macedonian, 63 | [StringValue("tl")] Tagalog, 64 | [StringValue("ht")] Haitian, 65 | [StringValue("pms")] Piedmontese, 66 | [StringValue("te(")] Telugu, 67 | [StringValue("ta")] Tamil, 68 | [StringValue("be-x-old")] BelarusianTaraškievica, 69 | [StringValue("be(")] Belarusian, 70 | [StringValue("br")] Breton, 71 | [StringValue("ceb")] Cebuano, 72 | [StringValue("lv")] Latvian, 73 | [StringValue("sq")] Albanian, 74 | [StringValue("jv")] Javanese, 75 | [StringValue("mg")] Malagasy, 76 | [StringValue("cy")] Welsh, 77 | [StringValue("mr")] Marathi, 78 | [StringValue("lb")] Luxembourgish, 79 | [StringValue("is")] Icelandic, 80 | [StringValue("bs")] Bosnian, 81 | [StringValue("yo")] Yoruba, 82 | [StringValue("an")] Aragonese, 83 | [StringValue("lmo")] Lombard, 84 | [StringValue("hy")] Armenian, 85 | [StringValue("fy")] WestFrisian, 86 | [StringValue("bpy")] BishnupriyaManipuri, 87 | [StringValue("ml")] Malayalam, 88 | [StringValue("pnb")] WesternPanjabi, 89 | [StringValue("sw")] Swahili, 90 | [StringValue("bn")] Bengali, 91 | [StringValue("io")] Ido, 92 | [StringValue("af")] Afrikaans, 93 | [StringValue("gu")] Gujarati, 94 | [StringValue("uz")] Uzbek, 95 | [StringValue("zh-yue")] Cantonese, 96 | [StringValue("ne")] Nepali, 97 | [StringValue("nds")] LowSaxon, 98 | [StringValue("ur")] Urdu, 99 | [StringValue("ku")] Kurdish, 100 | [StringValue("ast")] Asturian, 101 | [StringValue("scn")] Sicilian, 102 | [StringValue("su")] Sundanese, 103 | [StringValue("qu")] Quechua, 104 | [StringValue("diq")] Zazaki, 105 | [StringValue("ba")] Bashkir, 106 | [StringValue("tt")] Tatar, 107 | [StringValue("my")] Burmese, 108 | [StringValue("ga")] Irish, 109 | [StringValue("cv")] Chuvash, 110 | [StringValue("ia")] Interlingua, 111 | [StringValue("nap")] Neapolitan, 112 | [StringValue("bat-smg")] Samogitian, 113 | [StringValue("map-bms")] Banyumasan, 114 | [StringValue("wa")] Walloon, 115 | [StringValue("als")] Alemannic, 116 | [StringValue("am")] Amharic, 117 | [StringValue("kn")] Kannada, 118 | [StringValue("gd")] ScottishGaelic, 119 | [StringValue("bug")] Buginese, 120 | [StringValue("tg")] Tajik, 121 | [StringValue("zh-min-nan")] MinNan, 122 | [StringValue("yi")] Yiddish, 123 | [StringValue("sco")] Scots, 124 | [StringValue("vec")] Venetian, 125 | [StringValue("hif")] FijiHindi, 126 | [StringValue("roa-tara")] Tarantino, 127 | [StringValue("arz")] EgyptianArabic, 128 | [StringValue("os")] Ossetian, 129 | [StringValue("mzn")] Mazandarani, 130 | [StringValue("nah")] Nahuatl, 131 | [StringValue("ky")] Kirghiz, 132 | [StringValue("sah")] Sakha, 133 | [StringValue("mn")] Mongolian, 134 | [StringValue("sa")] Sanskrit, 135 | [StringValue("pam")] Kapampangan, 136 | [StringValue("hsb")] UpperSorbian, 137 | [StringValue("ckb")] Sorani, 138 | [StringValue("li")] Limburgian, 139 | [StringValue("mi")] Maori, 140 | [StringValue("si")] Sinhalese, 141 | [StringValue("co")] Corsican, 142 | [StringValue("gan")] Gan, 143 | [StringValue("glk")] Gilaki, 144 | [StringValue("bo")] Tibetan, 145 | [StringValue("fo")] Faroese, 146 | [StringValue("bar")] Bavarian, 147 | [StringValue("bcl")] CentralBicolano, 148 | [StringValue("ilo")] Ilokano, 149 | [StringValue("mrj")] HillMari, 150 | [StringValue("se")] NorthernSami, 151 | [StringValue("fiu-vro")] Võro, 152 | [StringValue("nds-nl")] DutchLowSaxon, 153 | [StringValue("tk")] Turkmen, 154 | [StringValue("vls")] WestFlemish, 155 | [StringValue("ps")] Pashto, 156 | [StringValue("gv")] Manx, 157 | [StringValue("rue")] Rusyn, 158 | [StringValue("dv")] Divehi, 159 | [StringValue("nrm")] Norman, 160 | [StringValue("pag")] Pangasinan, 161 | [StringValue("pa")] Punjabi, 162 | [StringValue("koi")] KomiPermyak, 163 | [StringValue("rm")] Romansh, 164 | [StringValue("km")] Khmer, 165 | [StringValue("kv")] Komi, 166 | [StringValue("xmf")] Mingrelian, 167 | [StringValue("csb")] Kashubian, 168 | [StringValue("udm")] Udmurt, 169 | [StringValue("mhr")] MeadowMari, 170 | [StringValue("fur")] Friulian, 171 | [StringValue("mt")] Maltese, 172 | [StringValue("zea")] Zeelandic, 173 | [StringValue("wuu")] Wu, 174 | [StringValue("lad")] Ladino, 175 | [StringValue("lij")] Ligurian, 176 | [StringValue("ug")] Uyghur, 177 | [StringValue("pi")] Pali, 178 | [StringValue("sc")] Sardinian, 179 | [StringValue("bh")] Bihari, 180 | [StringValue("zh-classical")] ClassicalChinese, 181 | [StringValue("or")] Oriya, 182 | [StringValue("nov")] Novial, 183 | [StringValue("ksh")] Ripuarian, 184 | [StringValue("frr")] NorthFrisian, 185 | [StringValue("ang")] AngloSaxon, 186 | [StringValue("so")] Somali, 187 | [StringValue("kw")] Cornish, 188 | [StringValue("stq")] SaterlandFrisian, 189 | [StringValue("nv")] Navajo, 190 | [StringValue("hak")] Hakka, 191 | [StringValue("ay")] Aymara, 192 | [StringValue("frp")] FrancoProvençalArpitan, 193 | [StringValue("ext")] Extremaduran, 194 | [StringValue("szl")] Silesian, 195 | [StringValue("pcd")] Picard, 196 | [StringValue("gag")] Gagauz, 197 | [StringValue("ie")] Interlingue, 198 | [StringValue("ln")] Lingala, 199 | [StringValue("haw")] Hawaiian, 200 | [StringValue("xal")] Kalmyk, 201 | [StringValue("rw")] Kinyarwanda, 202 | [StringValue("pdc")] PennsylvaniaGerman, 203 | [StringValue("vep")] Vepsian, 204 | [StringValue("pfl")] PalatinateGerman, 205 | [StringValue("krc")] KarachayBalkar, 206 | [StringValue("eml")] EmilianRomagnol, 207 | [StringValue("crh")] CrimeanTatar, 208 | [StringValue("gn")] Guarani, 209 | [StringValue("ace")] Acehnese, 210 | [StringValue("to")] Tongan, 211 | [StringValue("ce")] Chechen, 212 | [StringValue("kl")] Greenlandic, 213 | [StringValue("arc")] AssyrianNeoAramaic, 214 | [StringValue("myv")] Erzya, 215 | [StringValue("dsb")] LowerSorbian, 216 | [StringValue("as")] Assamese, 217 | [StringValue("bjn")] Banjar, 218 | [StringValue("pap")] Papiamentu, 219 | [StringValue("tpi")] TokPisin, 220 | [StringValue("lbe")] Lak, 221 | [StringValue("mdf")] Moksha, 222 | [StringValue("wo")] Wolof, 223 | [StringValue("jbo")] Lojban, 224 | [StringValue("sn")] Shona, 225 | [StringValue("kab")] Kabyle, 226 | [StringValue("av")] Avar, 227 | [StringValue("cbk-zam")] ZamboangaChavacano, 228 | [StringValue("ty")] Tahitian, 229 | [StringValue("srn")] Sranan, 230 | [StringValue("kbd")] KabardianCircassian, 231 | [StringValue("lez")] Lezgian, 232 | [StringValue("lo")] Lao, 233 | [StringValue("ab")] Abkhazian, 234 | [StringValue("tet")] Tetum, 235 | [StringValue("mwl")] Mirandese, 236 | [StringValue("ltg")] Latgalian, 237 | [StringValue("na")] Nauruan, 238 | [StringValue("ig")] Igbo, 239 | [StringValue("kg")] Kongo, 240 | [StringValue("za")] Zhuang, 241 | [StringValue("kaa")] Karakalpak, 242 | [StringValue("nso")] NorthernSotho, 243 | [StringValue("zu")] Zulu, 244 | [StringValue("rmy")] Romani, 245 | [StringValue("cu")] OldChurchSlavonic, 246 | [StringValue("tn")] Tswana, 247 | [StringValue("chy")] Cheyenne, 248 | [StringValue("chr")] Cherokee, 249 | [StringValue("got")] Gothic, 250 | [StringValue("sm")] Samoan, 251 | [StringValue("bi")] Bislama, 252 | [StringValue("mo")] Moldovan, 253 | [StringValue("iu")] Inuktitut, 254 | [StringValue("bm")] Bambara, 255 | [StringValue("pih")] Norfolk, 256 | [StringValue("ik")] Inupiak, 257 | [StringValue("ss")] Swati, 258 | [StringValue("sd")] Sindhi, 259 | [StringValue("pnt")] Pontic, 260 | [StringValue("cdo")] MinDong, 261 | [StringValue("ee")] Ewe, 262 | [StringValue("ha")] Hausa, 263 | [StringValue("ti")] Tigrinya, 264 | [StringValue("bxr")] BuryatRussia, 265 | [StringValue("ts")] Tsonga, 266 | [StringValue("om")] Oromo, 267 | [StringValue("ks")] Kashmiri, 268 | [StringValue("ki")] Kikuyu, 269 | [StringValue("ve(")] Venda, 270 | [StringValue("sg")] Sango, 271 | [StringValue("rn")] Kirundi, 272 | [StringValue("cr")] Cree, 273 | [StringValue("dz")] Dzongkha, 274 | [StringValue("lg")] Luganda, 275 | [StringValue("ak")] Akan, 276 | [StringValue("ff")] Fula, 277 | [StringValue("tum")] Tumbuka, 278 | [StringValue("fj")] Fijian, 279 | [StringValue("st")] Sesotho, 280 | [StringValue("tw")] Twi, 281 | [StringValue("xh")] Xhosa, 282 | [StringValue("ch")] Chamorro, 283 | [StringValue("ny")] Chichewa, 284 | [StringValue("ng")] Ndonga, 285 | [StringValue("ii")] SichuanYi, 286 | [StringValue("cho")] Choctaw, 287 | [StringValue("mh")] Marshallese, 288 | [StringValue("kj")] Kuanyama, 289 | [StringValue("ho")] HiriMotu, 290 | [StringValue("mus")] Muscogee, 291 | [StringValue("kr")] Kanuri, 292 | [StringValue("hz")] Herero 293 | } -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiNamespace.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | /// The Wikipedia namespaces. See https://en.wikipedia.org/wiki/Wikipedia:Namespace 6 | [Flags] 7 | public enum WikiNamespace : ulong 8 | { 9 | NotSet = 0, 10 | [StringValue("*")] All = 1 << 0, 11 | [StringValue("0")] Main = 1 << 1, 12 | [StringValue("1")] Talk = 1 << 2, 13 | [StringValue("2")] User = 1 << 3, 14 | [StringValue("3")] UserTalk = 1 << 4, 15 | [StringValue("4")] Wikipedia = 1 << 5, 16 | [StringValue("5")] WikipediaTalk = 1 << 6, 17 | [StringValue("6")] File = 1 << 7, 18 | [StringValue("7")] FileTalk = 1 << 8, 19 | [StringValue("8")] MediaWiki = 1 << 9, 20 | [StringValue("9")] MediaWikiTalk = 1 << 10, 21 | [StringValue("10")] Template = 1 << 11, 22 | [StringValue("11")] TemplateTalk = 1 << 12, 23 | [StringValue("12")] Help = 1 << 13, 24 | [StringValue("13")] HelpTalk = 1 << 14, 25 | [StringValue("14")] Category = 1 << 15, 26 | [StringValue("15")] CategoryTalk = 1 << 16, 27 | [StringValue("100")] Portal = 1 << 17, 28 | [StringValue("101")] PortalTalk = 1 << 18, 29 | [StringValue("118")] Draft = 1 << 19, 30 | [StringValue("119")] DraftTalk = 1 << 20, 31 | [StringValue("710")] TimedText = 1 << 21, 32 | [StringValue("711")] TimedTextTalk = 1 << 22, 33 | [StringValue("828")] Module = 1 << 23, 34 | [StringValue("829")] ModuleTalk = 1 << 24 35 | } -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiProperty.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | [Flags] 6 | public enum WikiProperty : uint 7 | { 8 | NotSet = 0, 9 | 10 | /// Adds the size of the page in bytes. 11 | [StringValue("size")] Size = 1 << 0, 12 | 13 | /// Adds the word count of the page. 14 | [StringValue("wordcount")] WordCount = 1 << 1, 15 | 16 | /// Adds the timestamp of when the page was last edited. 17 | [StringValue("timestamp")] Timestamp = 1 << 2, 18 | 19 | /// Adds a parsed snippet of the page. 20 | [StringValue("snippet")] Snippet = 1 << 3, 21 | 22 | /// Adds a parsed snippet of the page title. 23 | [StringValue("titlesnippet")] TitleSnippet = 1 << 4, 24 | 25 | /// Adds the title of the matching redirect. 26 | [StringValue("redirecttitle")] RedirectTitle = 1 << 5, 27 | 28 | /// Adds a parsed snippet of the redirect title. 29 | [StringValue("redirectsnippet")] RedirectSnippet = 1 << 6, 30 | 31 | /// Adds the title of the matching section. 32 | [StringValue("sectiontitle")] SectionTitle = 1 << 7, 33 | 34 | /// Adds a parsed snippet of the matching section title. 35 | [StringValue("sectionsnippet")] SectionSnippet = 1 << 8, 36 | 37 | /// Adds a boolean indicating if the search matched file content. 38 | [StringValue("isfilematch")] IsFileMatch = 1 << 9, 39 | 40 | /// Adds a parsed snippet of the matching category. 41 | [StringValue("categorysnippet")] CategorySnippet = 1 << 10, 42 | 43 | /// Adds extra data generated by extensions. 44 | [StringValue("extensiondata")] ExtensionData = 1 << 11, 45 | 46 | /// 47 | /// Include all properties 48 | /// 49 | All = uint.MaxValue 50 | } -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiQueryProfile.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | [Flags] 6 | public enum WikiQueryProfile 7 | { 8 | NotSet = 0, 9 | 10 | /// Ranking based on the number of incoming links, some templates, page language and recency 11 | /// (templates/language/recency may not be activated on this wiki). 12 | [StringValue("classic")] Classic = 1 << 0, 13 | 14 | /// Ranking based on some templates, page language and recency when activated on this wiki. 15 | [StringValue("classic_noboostlinks")] ClassicNoBoostLinks = 1 << 1, 16 | 17 | /// Ranking based solely on query dependent features (for debug only). 18 | [StringValue("empty")] Empty = 1 << 2, 19 | 20 | /// Weighted sum based on incoming links 21 | [StringValue("wsum_inclinks")] WeightedSumIncomingLinks = 1 << 3, 22 | 23 | /// Weighted sum based on incoming links and weekly pageviews 24 | [StringValue("wsum_inclinks_pv")] WeightedSumIncomingLinksAndPageViews = 1 << 4, 25 | 26 | /// Ranking based primarily on page views 27 | [StringValue("popular_inclinks_pv")] PopularIncomingLinksAndPageViews = 1 << 5, 28 | 29 | /// Ranking based primarily on incoming link counts 30 | [StringValue("popular_inclinks")] PopularIncomingLinks = 1 << 6, 31 | 32 | /// Let the search engine decide on the best profile to use. 33 | [StringValue("engine_autoselect")] AutoSelect = 1 << 7 34 | } -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiSortOrder.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | public enum WikiSortOrder 6 | { 7 | NotSet = 0, 8 | [StringValue("create_timestamp_asc")] CreateTimestampAsc, 9 | [StringValue("create_timestamp_desc")] CreateTimestampDesc, 10 | [StringValue("incoming_links_asc")] IncomingLinksAsc, 11 | [StringValue("incoming_links_desc")] IncomingLinksDesc, 12 | [StringValue("just_match")] JustMatch, 13 | [StringValue("last_edit_asc")] LastEditAsc, 14 | [StringValue("last_edit_desc")] LastEditDesc, 15 | [StringValue("none")] None, 16 | [StringValue("random")] Random, 17 | [StringValue("relevance")] Relevance, 18 | [StringValue("user_random")] UserRandom 19 | } -------------------------------------------------------------------------------- /src/Wikipedia/Enums/WikiWhat.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Internal; 2 | 3 | namespace Genbox.Wikipedia.Enums; 4 | 5 | public enum WikiWhat 6 | { 7 | NotSet = 0, 8 | 9 | /// Search in page titles (default) (if search engine doesn't support title searches, such as Lucene which is used 10 | /// by Wikipedia, then it falls back to text) 11 | [StringValue("title")] Title, 12 | 13 | /// Search in page text 14 | [StringValue("text")] Text, 15 | 16 | /// Search for titles that match exactly. Example: 'Microsoft' results in 'Microsoft', where 'Microsof' results in 17 | /// 'no results' 18 | [StringValue("nearmatch")] NearMatch 19 | } -------------------------------------------------------------------------------- /src/Wikipedia/Extensions/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Text; 3 | using Genbox.Wikipedia.Internal; 4 | 5 | namespace Genbox.Wikipedia.Extensions; 6 | 7 | public static class EnumExtensions 8 | { 9 | /// 10 | /// Convert a flags enum into a sequence of string values concatenated with |. E.g. Value1|Value2. 11 | /// 12 | public static string GetConcatValues(this Enum value) 13 | { 14 | StringBuilder sb = new StringBuilder(); 15 | 16 | foreach (Enum e in Enum.GetValues(value.GetType())) 17 | { 18 | //This library follows a convention where all enums have a NotSet value. We do not want that in our output 19 | if (string.Equals(e.ToString(), "NotSet", StringComparison.OrdinalIgnoreCase)) 20 | continue; 21 | 22 | //Same as above 23 | if (string.Equals(e.ToString(), "All", StringComparison.OrdinalIgnoreCase)) 24 | continue; 25 | 26 | if (value.HasFlag(e)) 27 | sb.Append(e.GetStringValue()).Append('|'); 28 | } 29 | 30 | return sb.ToString().TrimEnd('|'); 31 | } 32 | 33 | /// Will get the string value for a given enums value, this will only work if you assign the StringValue attribute 34 | /// to the items in your enum. Source: 35 | /// http://weblogs.asp.net/stefansedich/archive/2008/03/12/enum-with-string-values-in-c.aspx 36 | public static string GetStringValue(this Enum value) 37 | { 38 | // Get the type 39 | Type type = value.GetType(); 40 | 41 | // Get fieldinfo for this type 42 | FieldInfo fieldInfo = type.GetField(value.ToString()); 43 | 44 | // Get the stringvalue attributes 45 | StringValueAttribute[] attr = fieldInfo.GetCustomAttributes(false).ToArray(); 46 | 47 | if (attr.Length == 0) 48 | throw new InvalidOperationException("Unable to find StringValue attribute on " + type.Name); 49 | 50 | // Return the first if there was a match. 51 | return attr[0].StringValue; 52 | } 53 | } -------------------------------------------------------------------------------- /src/Wikipedia/Internal/LowerCasePolicy.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Genbox.Wikipedia.Internal; 4 | 5 | internal class LowerCasePolicy : JsonNamingPolicy 6 | { 7 | public static readonly JsonNamingPolicy Instance = new LowerCasePolicy(); 8 | 9 | private LowerCasePolicy() { } 10 | 11 | public override string ConvertName(string name) 12 | { 13 | return name.ToLowerInvariant(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Wikipedia/Internal/StringValueAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Genbox.Wikipedia.Internal; 2 | 3 | /// This attribute is used to represent a string value for a value in an enum. 4 | [AttributeUsage(AttributeTargets.Field)] 5 | internal class StringValueAttribute : Attribute 6 | { 7 | /// Constructor used to init a StringValue Attribute 8 | public StringValueAttribute(string value) 9 | { 10 | StringValue = value; 11 | } 12 | 13 | /// Holds the string value for a value in an enum. 14 | public string StringValue { get; } 15 | } -------------------------------------------------------------------------------- /src/Wikipedia/Internal/UrlHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text; 3 | 4 | namespace Genbox.Wikipedia.Internal; 5 | 6 | internal static class UrlHelper 7 | { 8 | //Valid URL characters according to RFC3986: https://tools.ietf.org/html/rfc3986#section-2.3 9 | private const string _validUrlCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; 10 | 11 | private static readonly HashSet _validUrlLookup = new HashSet(BuildLookup(_validUrlCharacters)); 12 | 13 | private static IEnumerable BuildLookup(string charList) 14 | { 15 | foreach (char c in charList) 16 | { 17 | string escaped = Uri.EscapeUriString(c.ToString(CultureInfo.InvariantCulture)); 18 | if (escaped.Length == 1 && escaped[0] == c) 19 | yield return (byte)c; 20 | } 21 | } 22 | 23 | public static string UrlPathEncode(string input) 24 | { 25 | string[] pathSegments = input.Split('/'); 26 | return string.Join("/", pathSegments.Select(UrlEncode)); 27 | } 28 | 29 | public static string UrlEncode(string data) 30 | { 31 | StringBuilder sb = new StringBuilder(); 32 | 33 | foreach (byte symbol in Encoding.UTF8.GetBytes(data)) 34 | { 35 | if (_validUrlLookup.Contains(symbol)) 36 | sb.Append((char)symbol); 37 | else 38 | sb.Append('%').AppendFormat(CultureInfo.InvariantCulture, "{0:X2}", symbol); 39 | } 40 | 41 | return sb.ToString(); 42 | } 43 | 44 | public static string CreateQueryString(IEnumerable> parameters, bool encode = true, bool outputEqualOnEmpty = false) 45 | { 46 | StringBuilder sb = new StringBuilder(); 47 | 48 | foreach (KeyValuePair item in parameters) 49 | { 50 | if (sb.Length > 0) 51 | sb.Append('&'); 52 | 53 | string encodedKey = encode ? UrlEncode(item.Key) : item.Key; 54 | 55 | if (string.IsNullOrEmpty(item.Value)) 56 | { 57 | sb.Append(encodedKey); 58 | 59 | if (outputEqualOnEmpty) 60 | sb.Append('='); 61 | } 62 | else 63 | sb.Append(encodedKey).Append('=').Append(encode ? UrlEncode(item.Value) : item.Value); 64 | } 65 | 66 | return sb.ToString(); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Wikipedia/Objects/Continuation.cs: -------------------------------------------------------------------------------- 1 | namespace Genbox.Wikipedia.Objects; 2 | 3 | public class Continuation 4 | { 5 | public int? SROffset { get; set; } 6 | public string? Continue { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Wikipedia/Objects/Error.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Genbox.Wikipedia.Objects; 4 | 5 | public class Error 6 | { 7 | public string? Code { get; set; } 8 | 9 | public string? Info { get; set; } 10 | 11 | [JsonPropertyName("docref")] 12 | public string? DocumentReference { get; set; } 13 | } -------------------------------------------------------------------------------- /src/Wikipedia/Objects/ModuleError.cs: -------------------------------------------------------------------------------- 1 | namespace Genbox.Wikipedia.Objects; 2 | 3 | public class ModuleError 4 | { 5 | public string? Code { get; set; } 6 | 7 | public string? Module { get; set; } 8 | 9 | public string? Html { get; set; } 10 | 11 | public string? Text { get; set; } 12 | 13 | public string? Key { get; set; } 14 | 15 | public IList? Params { get; set; } 16 | } -------------------------------------------------------------------------------- /src/Wikipedia/Objects/QueryResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Genbox.Wikipedia.Objects; 4 | 5 | public class QueryResult 6 | { 7 | public QueryResult() 8 | { 9 | SearchResults = new List(0); 10 | } 11 | 12 | [JsonPropertyName("search")] 13 | public List SearchResults { get; set; } 14 | public SearchInfo? SearchInfo { get; set; } 15 | } -------------------------------------------------------------------------------- /src/Wikipedia/Objects/SearchInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Genbox.Wikipedia.Objects; 2 | 3 | public class SearchInfo 4 | { 5 | public int TotalHits { get; set; } 6 | 7 | public string? Suggestion { get; set; } 8 | 9 | public string? SuggestionSnippet { get; set; } 10 | } -------------------------------------------------------------------------------- /src/Wikipedia/Objects/SearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Genbox.Wikipedia.Objects; 4 | 5 | public class SearchResult 6 | { 7 | //Default properties 8 | [JsonPropertyName("ns")] 9 | public int Namespace { get; set; } 10 | 11 | public string Title { get; set; } = string.Empty; 12 | 13 | public int PageId { get; set; } 14 | 15 | public int Size { get; set; } 16 | 17 | //Other properties 18 | public int? WordCount { get; set; } 19 | 20 | public DateTimeOffset? TimeStamp { get; set; } 21 | 22 | public string? Snippet { get; set; } 23 | 24 | public string? TitleSnippet { get; set; } 25 | 26 | public string? RedirectTitle { get; set; } 27 | 28 | public string? RedirectSnippet { get; set; } 29 | 30 | public string? SectionTitle { get; set; } 31 | 32 | public string? SectionSnippet { get; set; } 33 | 34 | public bool? IsFileMatch { get; set; } 35 | 36 | public string? CategorySnippet { get; set; } 37 | 38 | public string? ExtensionData { get; set; } 39 | 40 | /// The URI that points to the wikipedia page that contains the title. Note: Normalization of the title occurs 41 | /// automatically. 42 | public Uri Url { get; set; } = null!; 43 | } -------------------------------------------------------------------------------- /src/Wikipedia/WikiMediaRequest.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Enums; 2 | 3 | namespace Genbox.Wikipedia; 4 | 5 | public abstract class WikiMediaRequest 6 | { 7 | /// Verify that the user is logged in if set to user, not logged in if set to anon, or has the bot user right if 8 | /// bot. 9 | public string? Assert { get; set; } 10 | 11 | /// Verify the current user is the named user. 12 | public string? AssertUser { get; set; } 13 | 14 | /// Include the hostname that served the request in the results. 15 | public bool IncludeServedBy { get; set; } 16 | 17 | /// Include the current timestamp in the result. 18 | public bool IncludeCurrentTimestamp { get; set; } 19 | 20 | /// Include the languages used for uselang and errorlang in the result. 21 | public bool IncludeLanguageUsed { get; set; } 22 | 23 | /// Any value given here will be included in the response. May be used to distinguish requests. 24 | public string? RequestId { get; set; } 25 | 26 | /// Language to use for message translations. action=query&meta=siteinfo with siprop=languages returns a list of 27 | /// language codes, or specify user to use the current user's language preference, or specify content to use this wiki's 28 | /// content language. 29 | public string? LanguageToUse { get; set; } 30 | 31 | /// Variant of the language. Only works if the base language supports variant conversion. 32 | public string? LanguageVariant { get; set; } 33 | 34 | /// Format to use for warning and error text output 35 | public WikiErrorFormat ErrorFormat { get; set; } 36 | 37 | /// Language to use for warnings and errors. action=query&meta=siteinfo with siprop=languages returns a list of 38 | /// language codes, or specify content to use this wiki's content language, or specify uselang to use the same value as the 39 | /// uselang parameter. 40 | public string? ErrorLanguageToUse { get; set; } 41 | 42 | /// If given, error texts will use locally-customized messages from the MediaWiki namespace. 43 | public bool ErrorUseLocalLanguage { get; set; } 44 | } -------------------------------------------------------------------------------- /src/Wikipedia/WikiSearchRequest.cs: -------------------------------------------------------------------------------- 1 | using Genbox.Wikipedia.Enums; 2 | 3 | namespace Genbox.Wikipedia; 4 | 5 | public class WikiSearchRequest : WikiMediaRequest 6 | { 7 | //See https://www.mediawiki.org/wiki/API:Search 8 | 9 | public WikiSearchRequest(string query) 10 | { 11 | Query = query; 12 | } 13 | 14 | /// Search for page titles or content matching this value. You can use the search string to invoke special search 15 | /// features, depending on what the wiki's search backend implements. 16 | public string Query { get; set; } 17 | 18 | /// The namespace(s) to enumerate. Defaults to . 19 | public WikiNamespace NamespacesToInclude { get; set; } 20 | 21 | /// How many total pages to return. Default: 10, Max: 500 22 | public int Limit { get; set; } 23 | 24 | /// When more results are available, use this to continue. Default: 0 25 | public int Offset { get; set; } 26 | 27 | /// Query independent profile to use (affects ranking algorithm). Defaults to 28 | /// 29 | public WikiQueryProfile QueryIndependentProfile { get; set; } 30 | 31 | /// Which type of search to perform. 32 | public WikiWhat WhatToSearch { get; set; } 33 | 34 | /// What metadata to return. Default: TotalHits, Suggestion 35 | public WikiInfo InfoToInclude { get; set; } 36 | 37 | /// What property to include in the results. Defaults to a combination of snippet, size, word count and timestamp 38 | public WikiProperty PropertiesToInclude { get; set; } 39 | 40 | /// Include InterWiki results in the search, if available. 41 | public bool IncludeInterWikiResults { get; set; } 42 | 43 | /// Enable internal query rewriting. Some search backends can rewrite the query into another which is thought to 44 | /// provide better results, for instance by correcting spelling errors. 45 | public bool EnableRewrites { get; set; } 46 | 47 | /// Set the sort order of returned results. Defaults to Relevance. 48 | public WikiSortOrder SortOrder { get; set; } 49 | 50 | /// What language to use. Default: English (en) 51 | public WikiLanguage WikiLanguage { get; set; } 52 | 53 | public bool TryValidate(out string? message) 54 | { 55 | if (Limit > 500) 56 | { 57 | message = nameof(Limit) + " must be between 1 and 500"; 58 | return false; 59 | } 60 | 61 | if (string.IsNullOrEmpty(Query)) 62 | { 63 | message = nameof(Query) + " must be set to a value"; 64 | return false; 65 | } 66 | 67 | if (WikiLanguage == WikiLanguage.NotSet) 68 | { 69 | message = nameof(WikiLanguage) + " must be set to a valid value"; 70 | return false; 71 | } 72 | 73 | message = null; 74 | return true; 75 | } 76 | } -------------------------------------------------------------------------------- /src/Wikipedia/WikiSearchResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Genbox.Wikipedia.Objects; 3 | 4 | namespace Genbox.Wikipedia; 5 | 6 | public class WikiSearchResponse 7 | { 8 | public bool BatchComplete { get; set; } 9 | public Continuation? Continue { get; set; } 10 | 11 | [JsonPropertyName("query")] 12 | public QueryResult? QueryResult { get; set; } 13 | public Error? Error { get; set; } 14 | 15 | [JsonPropertyName("errors")] 16 | public IList? ModuleErrors { get; set; } 17 | public string? ServedBy { get; set; } 18 | public string? RequestId { get; set; } 19 | 20 | [JsonPropertyName("errorlang")] 21 | public string? ErrorLanguage { get; set; } 22 | 23 | [JsonPropertyName("uselang")] 24 | public string? Language { get; set; } 25 | 26 | [JsonPropertyName("curtimestamp")] 27 | public DateTimeOffset CurrentTimestamp { get; set; } 28 | } -------------------------------------------------------------------------------- /src/Wikipedia/Wikipedia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Wikipedia/WikipediaClient.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Genbox.Wikipedia.Enums; 3 | using Genbox.Wikipedia.Extensions; 4 | using Genbox.Wikipedia.Internal; 5 | using Genbox.Wikipedia.Objects; 6 | 7 | namespace Genbox.Wikipedia; 8 | 9 | public class WikipediaClient : IDisposable 10 | { 11 | private readonly HttpClient? _httpClient; 12 | private readonly JsonSerializerOptions _options; 13 | private readonly HttpClient? _userClient; 14 | 15 | public WikipediaClient(HttpClient? client = null) 16 | { 17 | _userClient = client; 18 | 19 | if (_userClient == null) 20 | _httpClient = new HttpClient(); 21 | 22 | _options = new JsonSerializerOptions(); 23 | _options.PropertyNamingPolicy = LowerCasePolicy.Instance; 24 | } 25 | 26 | ///Set to true to use HTTPS instead of HTTP. Defaults to true. 27 | public bool UseTls { get; set; } = true; 28 | 29 | ///The default language to use. Can be overriden on each request. 30 | public WikiLanguage DefaultLanguage { get; set; } = WikiLanguage.English; 31 | 32 | public void Dispose() 33 | { 34 | Dispose(true); 35 | GC.SuppressFinalize(this); 36 | } 37 | 38 | protected virtual void Dispose(bool disposing) 39 | { 40 | if (disposing) 41 | _httpClient?.Dispose(); 42 | } 43 | 44 | public async Task SearchAsync(string query, CancellationToken token = default) 45 | { 46 | return await SearchAsync(new WikiSearchRequest(query), token).ConfigureAwait(false); 47 | } 48 | 49 | public async Task SearchAsync(WikiSearchRequest request, CancellationToken token = default) 50 | { 51 | if (request.WikiLanguage == WikiLanguage.NotSet) 52 | request.WikiLanguage = DefaultLanguage; 53 | 54 | HttpClient? client = _userClient ?? _httpClient; 55 | 56 | if (client == null) 57 | throw new InvalidOperationException("Bug check: HttpClient is null"); 58 | 59 | using HttpRequestMessage httpReq = CreateHttpRequest(request); 60 | using HttpResponseMessage? httpResp = await client.SendAsync(httpReq, token).ConfigureAwait(false); 61 | using Stream? contentStream = await httpResp.Content.ReadAsStreamAsync().ConfigureAwait(false); 62 | 63 | WikiSearchResponse? searchResp = await JsonSerializer.DeserializeAsync(contentStream, _options, token).ConfigureAwait(false); 64 | 65 | if (searchResp == null) 66 | throw new InvalidOperationException("Unable to read query response"); 67 | 68 | QueryResult? query = searchResp.QueryResult; 69 | 70 | //We do this to ensure users are not bothered with nullable responses 71 | if (query == null) 72 | query = new QueryResult(); 73 | 74 | //For convenience, we autocreate uris that point directly to the wiki page. 75 | string prefix = UseTls ? "https://" : "http://"; 76 | 77 | foreach (SearchResult? search in query.SearchResults) 78 | search.Url = new Uri(prefix + request.WikiLanguage.GetStringValue() + ".wikipedia.org/wiki/" + search.Title); 79 | 80 | return searchResp; 81 | } 82 | 83 | private HttpRequestMessage CreateHttpRequest(WikiSearchRequest searchRequest) 84 | { 85 | if (!searchRequest.TryValidate(out string? message)) 86 | throw new ArgumentException(message, nameof(searchRequest)); 87 | 88 | //Required 89 | Dictionary queryParams = new Dictionary 90 | { 91 | {"action", "query"}, 92 | {"list", "search"}, //See https://www.mediawiki.org/w/api.php?action=help&modules=query%2Bsearch 93 | {"srsearch", searchRequest.Query}, 94 | {"format", "json"}, 95 | {"formatversion", "2"} //See https://www.mediawiki.org/wiki/API:JSON_version_2 96 | }; 97 | 98 | MapWikiMediaRequest(searchRequest, queryParams); 99 | 100 | //Optional 101 | if (searchRequest.NamespacesToInclude != WikiNamespace.NotSet) 102 | queryParams.Add("srnamespace", searchRequest.NamespacesToInclude.GetConcatValues()); 103 | 104 | if (searchRequest.Limit != 0) 105 | queryParams.Add("srlimit", searchRequest.Limit.ToString()); 106 | 107 | if (searchRequest.Offset != 0) 108 | queryParams.Add("sroffset", searchRequest.Offset.ToString()); 109 | 110 | if (searchRequest.QueryIndependentProfile != WikiQueryProfile.NotSet) 111 | queryParams.Add("srqiprofile", searchRequest.QueryIndependentProfile.GetStringValue()); 112 | 113 | if (searchRequest.WhatToSearch != WikiWhat.NotSet) 114 | queryParams.Add("srwhat", searchRequest.WhatToSearch.GetStringValue()); 115 | 116 | if (searchRequest.InfoToInclude != WikiInfo.NotSet) 117 | queryParams.Add("srinfo", searchRequest.InfoToInclude.GetConcatValues()); 118 | 119 | if (searchRequest.PropertiesToInclude != WikiProperty.NotSet) 120 | queryParams.Add("srprop", searchRequest.PropertiesToInclude.GetConcatValues()); 121 | 122 | if (searchRequest.IncludeInterWikiResults) 123 | queryParams.Add("srinterwiki", "true"); 124 | 125 | if (searchRequest.EnableRewrites) 126 | queryParams.Add("srenablerewrites", "true"); 127 | 128 | if (searchRequest.SortOrder != WikiSortOrder.NotSet) 129 | queryParams.Add("srsort", searchRequest.SortOrder.GetStringValue()); 130 | 131 | //API example: http://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=wikipedia&srprop=timestamp 132 | Uri baseUrl = new Uri(string.Format(UseTls ? "https://{0}.wikipedia.org/w/" : "http://{0}.wikipedia.org/w/", searchRequest.WikiLanguage.GetStringValue())); 133 | 134 | return new HttpRequestMessage(HttpMethod.Get, new Uri(baseUrl, "api.php?" + UrlHelper.CreateQueryString(queryParams))); 135 | } 136 | 137 | private void MapWikiMediaRequest(WikiMediaRequest request, Dictionary queryParams) 138 | { 139 | if (request.Assert != null) 140 | queryParams.Add("assert", request.Assert); 141 | 142 | if (request.AssertUser != null) 143 | queryParams.Add("assertuser", request.AssertUser); 144 | 145 | if (request.ErrorFormat != WikiErrorFormat.NotSet) 146 | queryParams.Add("errorformat", request.ErrorFormat.GetStringValue()); 147 | 148 | if (request.ErrorLanguageToUse != null) 149 | queryParams.Add("errorlang", request.ErrorLanguageToUse); 150 | 151 | if (request.ErrorUseLocalLanguage) 152 | queryParams.Add("errorsuselocal", "true"); 153 | 154 | if (request.IncludeCurrentTimestamp) 155 | queryParams.Add("curtimestamp", "true"); 156 | 157 | if (request.IncludeLanguageUsed) 158 | queryParams.Add("responselanginfo", "true"); 159 | 160 | if (request.IncludeServedBy) 161 | queryParams.Add("servedby", "true"); 162 | 163 | if (request.LanguageToUse != null) 164 | queryParams.Add("uselang", request.LanguageToUse); 165 | 166 | if (request.LanguageVariant != null) 167 | queryParams.Add("variant", request.LanguageVariant); 168 | 169 | if (request.RequestId != null) 170 | queryParams.Add("requestid", request.RequestId); 171 | } 172 | } --------------------------------------------------------------------------------