Skip to content

[MediaWiki] Getting started

Chen edited this page Mar 21, 2021 · 11 revisions

Applies to: WCL v0.6.0

Library references

Connection, login and logout

  • Note that since C# 7.1, you can write static async Task Main() for your application entry point.
  • Note that since C# 9.0, you can leverage top-level statements in order to start a MediaWiki bot really quick.
  • If you are working with FANDOM / Wikia sites, prefer WikiaSite to WikiSite since the former one provides some more Wikia-specific support.
class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    static async Task MainAsync()
    {
        // A WikiClient has its own CookieContainer.
        var client = new WikiClient
        {
            ClientUserAgent = "WCLQuickStart/1.0 (your user name or contact information here)"
        };
        // You can create multiple WikiSite instances on the same WikiClient to share the state.
        var site = new WikiSite(client, "https://test2.wikipedia.org/w/api.php");
        // Wait for initialization to complete.
        // Throws error if any.
        await site.Initialization;
        try
        {
            await site.LoginAsync("User name", "password");
        }
        catch (WikiClientException ex)
        {
            Console.WriteLine(ex.Message);
            // Add your exception handler for failed login attempt.
        }

        // Do what you want
        Console.WriteLine(site.SiteInfo.SiteName);
        Console.WriteLine(site.AccountInfo);
        Console.WriteLine("{0} extensions", site.Extensions.Count);
        Console.WriteLine("{0} interwikis", site.InterwikiMap.Count);
        Console.WriteLine("{0} namespaces", site.Namespaces.Count);

        // We're done here
        await site.LogoutAsync();
        client.Dispose();        // Or you may use `using` statement.
    }

}

WikiPages

In WCL, pages on MediaWiki site is represented by WikiPage. When you instantiates a WikiPage, it contains no information other than the page title or ID given in constructor, until WikiPage.RefreshAsync is invoked. Still, you can edit/move/delete the page without invoking RefreshAsync beforehand since a page title/ID is sufficient for such operations.

Query

static async Task GetPageInformationAsync()
{
    var page = new WikiPage(myWikiSite, "france");
    // Fetch basic information and content of 1 page from server
    await page.RefreshAsync(PageQueryOptions.FetchContent
                            | PageQueryOptions.ResolveRedirects);
    Console.WriteLine(page.Title);
    Console.WriteLine(page.Content.Substring(0, 100));

    // Suppose you do not know the page is a normal page, category page, or file page.
    var catPage = new WikiPage(myWikiSite, "Category:Citation templates");
    // Only fetch for basic information.
    await catPage.RefreshAsync();
    var catInfo = catPage.GetPropertyGroup<CategoryInfoPropertyGroup>();
    // To list the pages under this category, see `generators` and CategoryMembersGenerator
    Console.WriteLine("{0}: {1} members, {2} pages, {3} files, {4} sub-categories",
        catPage, catInfo.MembersCount, catInfo.PagesCount, catInfo.FilesCount, catInfo.SubcategoriesCount);

    // Instantiates a page from page ID
    var pageFromId = new WikiPage(myWikiSite, 12345);
    await pageFromId.RefreshAsync();
    Console.WriteLine("Page: {0}, Id: {1}", pageFromId.Title, pageFromId.Id);

    // Yes, we can refresh multiple pages in a batch.
    var pages = new[] {"Main Page", "Project:Sandbox", "abcdef"}
        .Select(title => new WikiPage(myWikiSite, title))
        .ToArray();
    await pages.RefreshAsync();
    foreach (var p in pages)
    {
        Console.WriteLine("{0}: Exists? {1}", p, p.Exists);
    }
}

Edit

static async Task EditPageAsync()
{
    var page = new WikiPage(myWikiSite, "Project:Sandbox");
    // If you want to remove all the content there before you came,
    // you can UpdateContentAsync without RefreshAsync beforehand.
    await page.RefreshAsync(PageQueryOptions.FetchContent);
    page.Content += "\n\nThis is a test.";
    await page.UpdateContentAsync("Test edit from WCL quick start.", minor: false, bot: true);
}

File upload

Note that the StreamUploadSource below can be replaced with any other WikiUploadSource-derived classes.

static async Task UploadFileAsync()
{
    using (var s = File.OpenRead("AwesomeImage.png"))
    {
        var source = new StreamUploadSource(s);
        var suppressWarnings = false;
        RETRY:
        try
        {
            var result = await myWikiSite.UploadAsync("File:Sandbox.png", source, @"{{Information
|description = Upload test
|permission = {{Fairuse}}
}}", suppressWarnings);
            if (result.ResultCode == UploadResultCode.Warning)
            {
                Console.WriteLine(result.Warnings.ToString());
                Console.WriteLine("Press any key to suppress the warnings and try again.");
                Console.ReadKey();
                suppressWarnings = true;
                goto RETRY;
            }
        }
        catch (OperationFailedException ex)
        {
            // Since MW 1.31, if you are uploading the exactly same content to the same title with ignoreWarnings set to true, you will reveive this exception with ErrorCode set to fileexists-no-change.
            // See https://gerrit.wikimedia.org/r/378702.
            Console.WriteLine(ex.Message);
        }
    }
}

Chunked file upload

Since MediaWiki 1.19+, you can stash a large file to the server by chunks using ChunkedUploadSource, before uploading it. The following snippet is taken from the unit test project. Note that the default DefaultChunkSize is 1024*1024 (1MB), which is suitable in most cases.

var chunked = new ChunkedUploadSource(site, file.ContentStream) { /* DefaultChunkSize = 1024 * 4 */ };
do
{
    var result = await chunked.StashNextChunkAsync();
    Assert.NotEqual(UploadResultCode.Warning, result.ResultCode);
    if (result.ResultCode == UploadResultCode.Success)
    {
        // Some unit test assertions.
        Assert.True(result.FileRevision.IsAnonymous);
        Assert.Equal(file.Sha1, result.FileRevision.Sha1, StringComparer.OrdinalIgnoreCase);
    }
} while (!chunked.IsStashed);
try
{
    await site.UploadAsync("Test image.jpg", chunked, file.Description, true);
}
catch (OperationFailedException ex) when (ex.ErrorCode == "fileexists-no-change")
{
    // We cannot suppress this error by setting ignoreWarnings = true.
    Output.WriteLine(ex.Message);
}

Other operations

You can MoveAsync and DeleteAsync a page, so long as you have the permission.

Utilities

WikiPageStub structure

If you would like only to determine whether a page exists, the namespace ID of the page, or convert the page title from/to page ID, without querying further information, you may use WikiPageStub.FromPageTitles and WikiPageStub.FromPageIds static methods.

WikiLink class

If you would like only to normalize/parse/analyze a given page title consider using WikiLink.Parse static method. See WikiLinkTests.cs for example.

Some more example code

You can find a demo in ConsoleTestApplication1. It's also suggested that you take a look at UnitTestProject1 to find out what can be done with the library.

static async Task HelloWikiWorld()
{
    // Create a MediaWiki API client.
    var wikiClient = new WikiClient
    {
        // UA of Client Application. The UA of WikiClientLibrary will
        // be append to the end of this when sending requests.
        ClientUserAgent = "ConsoleTestApplication1/1.0",
    };
    // Create a MediaWiki Site instance with the URL of API endpoint.
    var site = await WikiSite.CreateAsync(wikiClient, "https://test2.wikipedia.org/w/api.php");
    // Access site information via WikiSite.SiteInfo
    Console.WriteLine("API version: {0}", site.SiteInfo.Generator);
    // Access user information via WikiSite.UserInfo
    Console.WriteLine("Hello, {0}!", site.AccountInfo.Name);
    // Site login
    Console.WriteLine("We will edit [[Project:Sandbox]].");
    if (Confirm($"Do you want to login into {site.SiteInfo.SiteName}?"))
    {
        LOGIN_RETRY:
        try
        {
        await site.LoginAsync(Input("User name"), Input("Password"));
        }
        catch (OperationFailedException ex)
        {
            Console.WriteLine(ex.ErrorMessage);
            goto LOGIN_RETRY;
        }
        Console.WriteLine("You have successfully logged in as {0}.", site.AccountInfo.Name);
        Console.WriteLine("You're in the following groups: {0}.", string.Join(",", site.AccountInfo.Groups));
    }
    // Find out more members in Site class, such as
    //  page.Namespaces
    //  page.InterwikiMap

    // Page Operations
    // Fetch information and content
    var page = new WikiPage(site, site.SiteInfo.MainPage);
    Console.WriteLine("Retriving {0}...", page);
    await page.RefreshAsync(PageQueryOptions.FetchContent);

    Console.WriteLine("Last touched at {0}.", page.LastTouched);
    Console.WriteLine("Last revision {0} by {1} at {2}.", page.LastRevisionId,
        page.LastRevision.UserName, page.LastRevision.TimeStamp);
    Console.WriteLine("Content length: {0} bytes ----------", page.ContentLength);
    Console.WriteLine(page.Content);
    // Purge the page
    if (await page.PurgeAsync())
        Console.WriteLine("  The page has been purged successfully.");
    // Edit the page
    page = new WikiPage(site, "Project:Sandbox");
    await page.RefreshAsync(PageQueryOptions.FetchContent);
    if (!page.Exists) Console.WriteLine("Warning: The page {0} doesn't exist.", page);
    page.Content += "\n\n'''Hello''' ''world''!";
    await page.UpdateContentAsync("Test edit from WikiClientLibrary.");
    Console.WriteLine("{0} has been saved. RevisionId = {1}.", page, page.LastRevisionId);
    // Find out more operations in WikiPage class, such as
    //  page.MoveAsync()
    //  page.DeleteAsync()
    // Logout
    await site.LogoutAsync();
    Console.WriteLine("You have successfully logged out.");
}

Here's the output

API version: MediaWiki 1.30.0-wmf.17
Hello, 206.161.*.*!
We will edit [[Project:Sandbox]].
Do you want to login into Wikipedia?[Y/N]> Y
User name> XuesongBot
Password> ******
You have successfully logged in as XuesongBot.
You're in the following groups: bot,editor,*,user,autoconfirmed.
Retriving Main Page...
Last touched at 2017/8/27 PM 4:25:06.
Last revision 318038 by MacFan4000 at 2017/5/27 PM 1:07:11.
Content length: 3687 bytes ----------
Welcome to '''test2.wikipedia.org'''! This wiki is currently running a test release of the {{CURRENTVERSION}} version of MediaWiki.

== What is the purpose of Test2? ==
Test2 is primarily used to trial and debug global and cross-wiki features in conjunction with <span class="plainlinks">https://test.wikipedia.org</span> and <span class="plainlinks">https://test.wikidata.org</span>.

[more]

  The page has been purged successfully.
Wikipedia:Sandbox has been saved. RevisionId = 327700.
You have successfully logged out.
Press any key to continue. . .