/// <summary>
        /// Asynchronously uploads a file in this title.
        /// </summary>
        /// <param name="site"></param>
        /// <param name="title"></param>
        /// <param name="source">Source of the file.</param>
        /// <param name="comment">Comment of the upload, as well as the page content if it doesn't exist.</param>
        /// <param name="ignoreWarnings">Ignore any warnings. This must be set to upload a new version of an existing image.</param>
        /// <param name="watch">Whether to add the file into your watchlist.</param>
        /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned task.</param>
        /// <exception cref="UnauthorizedAccessException">You do not have the permission to upload the file.</exception>
        /// <exception cref="OperationFailedException">
        /// There's an general failure while uploading the file.
        /// - or -
        /// Since MW 1.31, if you are uploading the exactly same content to the same title
        /// with <paramref name="ignoreWarnings"/> set to <c>true</c>,
        /// you will reveive this exception with <see cref="OperationFailedException.ErrorCode"/>
        /// set to <c>fileexists-no-change</c>. See https://gerrit.wikimedia.org/r/378702 .
        /// </exception>
        /// <exception cref="TimeoutException">Timeout specified in <see cref="WikiClient.Timeout"/> has been reached.</exception>
        /// <returns>An <see cref="UploadResult"/>. You need to check <see cref="UploadResult.ResultCode"/> for further action.</returns>
        public static async Task <UploadResult> UploadAsync(this WikiSite site, string title, WikiUploadSource source, string comment, bool ignoreWarnings,
                                                            AutoWatchBehavior watch, CancellationToken cancellationToken)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            Debug.Assert(source != null);
            var link = WikiLink.Parse(site, title, BuiltInNamespaces.File);

            using (site.BeginActionScope(null, title, source))
            {
                var requestFields = new Dictionary <string, object>
                {
                    { "action", "upload" },
                    { "watchlist", watch },
                    { "token", WikiSiteToken.Edit },
                    { "filename", link.Title },
                    { "comment", comment },
                    { "ignorewarnings", ignoreWarnings },
                };
                foreach (var p in source.GetUploadParameters(site.SiteInfo))
                {
                    requestFields[p.Key] = p.Value;
                }
                var request = new MediaWikiFormRequestMessage(requestFields, true);
                site.Logger.LogDebug("Start uploading.");
                var jresult = await site.InvokeMediaWikiApiAsync(request, cancellationToken);

                var result = jresult["upload"].ToObject <UploadResult>(Utility.WikiJsonSerializer);
                site.Logger.LogInformation("Uploaded. Result={Result}.", result.ResultCode);
                return(result);
            }
        }
Example #2
0
 public static string?ToWikiQueryValue(object?value)
 {
     return(value switch
     {
         null => null,
         string _ => (string)value,
         bool b => b ? "" : null,
         AutoWatchBehavior awb => awb switch
         {
             AutoWatchBehavior.Default => "preferences",
             AutoWatchBehavior.None => "nochange",
             AutoWatchBehavior.Watch => "watch",
             AutoWatchBehavior.Unwatch => "unwatch",
             _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
         },
Example #3
0
        /// <summary>
        /// Deletes the current page.
        /// </summary>
        public async Task <bool> DeleteAsync(string reason, AutoWatchBehavior watch, CancellationToken cancellationToken)
        {
            using (Site.BeginActionScope(this))
                using (await Site.ModificationThrottler.QueueWorkAsync("Delete: " + this, cancellationToken))
                {
                    JToken jresult;
                    try
                    {
                        jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new
                        {
                            action = "delete",
                            token = WikiSiteToken.Delete,
                            title = PageStub.HasTitle ? PageStub.Title : null,
                            pageid = PageStub.HasTitle ? null : (int?)PageStub.Id,
                            maxlag = 5,
                            watchlist = watch,
                            reason = reason,
                        }), cancellationToken);
                    }
                    catch (OperationFailedException ex)
                    {
                        switch (ex.ErrorCode)
                        {
                        case "cantdelete": // Couldn't delete "title". Maybe it was deleted already by someone else
                        case "missingtitle":
                            return(false);

                        case "permissiondenied":
                            throw new UnauthorizedOperationException(ex);
                        }
                        throw;
                    }
                    var title = (string)jresult["delete"]["title"];
                    PageStub       = new WikiPageStub(WikiPageStub.MissingPageIdMask, PageStub.Title, PageStub.NamespaceId);
                    LastRevision   = null;
                    LastRevisionId = 0;
                    Site.Logger.LogInformation("[[{Page}]] has been deleted.", title);
                    return(true);
                }
        }
Example #4
0
        /// <summary>
        /// Deletes the current page.
        /// </summary>
        public async Task <bool> DeleteAsync(string reason, AutoWatchBehavior watch, CancellationToken cancellationToken)
        {
            using (await Site.ModificationThrottler.QueueWorkAsync("Delete: " + this, cancellationToken))
            {
                var token = await Site.GetTokenAsync("delete", cancellationToken);

                JToken jresult;
                try
                {
                    jresult = await Site.PostValuesAsync(new
                    {
                        action    = "delete",
                        title     = Title,
                        maxlag    = 5,
                        watchlist = watch,
                        reason    = reason,
                        token     = token,
                    }, cancellationToken);
                }
                catch (OperationFailedException ex)
                {
                    switch (ex.ErrorCode)
                    {
                    case "cantdelete":     // Couldn't delete "title". Maybe it was deleted already by someone else
                    case "missingtitle":
                        return(false);

                    case "permissiondenied":
                        throw new UnauthorizedOperationException(ex);
                    }
                    throw;
                }
                var title = (string)jresult["delete"]["title"];
                Exists         = false;
                LastRevision   = null;
                LastRevisionId = 0;
                Site.Logger?.Info(this, $"Page {title} has been deleted.");
                return(true);
            }
        }
Example #5
0
        //content can be
        //  Stream          file content
        //  string          url to fetch
        //  UploadResult    the previous failed upload
        private static async Task <UploadResult> UploadAsyncInternal(WikiSite site, object content, string title, string comment,
                                                                     bool ignoreWarnings, AutoWatchBehavior watch, CancellationToken cancellationToken)
        {
            if (site == null)
            {
                throw new ArgumentNullException(nameof(site));
            }
            if (content == null)
            {
                throw new ArgumentNullException(nameof(content));
            }
            if (title == null)
            {
                throw new ArgumentNullException(nameof(title));
            }
            var link = WikiLink.Parse(site, title);

            if (link.Namespace.Id != BuiltInNamespaces.File)
            {
                throw new ArgumentException($"Invalid namespace for file title: {title} .", nameof(title));
            }
            var token = await site.GetTokenAsync("edit");

            long?streamPosition = null;

            HttpContent RequestFactory()
            {
                var requestContent = new MultipartFormDataContent
                {
                    { new StringContent("json"), "format" },
                    { new StringContent("upload"), "action" },
                    { new StringContent(Utility.ToWikiQueryValue(watch)), "watchlist" },
                    { new StringContent(token), "token" },
                    { new StringContent(link.Title), "filename" },
                    { new StringContent(comment), "comment" },
                };

                if (content is Stream streamContent)
                {
                    if (streamPosition < 0)
                    {
                        return(null);
                    }
                    // Memorize/reset the stream position.
                    if (streamContent.CanSeek)
                    {
                        if (streamPosition == null)
                        {
                            streamPosition = streamContent.Position;
                        }
                        else
                        {
                            streamContent.Position = streamPosition.Value;
                        }
                        Debug.Assert(streamPosition >= 0);
                    }
                    else
                    {
                        // Mark for do-not-retry.
                        streamPosition = -1;
                    }
                    requestContent.Add(new KeepAlivingStreamContent(streamContent), "file", title);
                }
                else if (content is string stringContent)
                {
                    requestContent.Add(new StringContent(stringContent), "url");
                }
                else if (content is UploadResult resultContent)
                {
                    var key = (resultContent).FileKey;
                    if (string.IsNullOrEmpty(key))
                    {
                        throw new InvalidOperationException("The specified UploadResult has no valid FileKey.");
                    }
                    // sessionkey: Same as filekey, maintained for backward compatibility (deprecated in 1.18)
                    requestContent.Add(new StringContent(key),
                                       site.SiteInfo.Version >= new Version(1, 18) ? "filekey" : "sessionkey");
                }
                else
                {
                    Debug.Assert(false, "Unrecognized content argument type.");
                }
                if (ignoreWarnings)
                {
                    requestContent.Add(new StringContent(""), "ignorewarnings");
                }
                return(requestContent);
            }

            site.Logger?.Info(site, $"Uploading: {link.Title} .");
            var jresult = await site.PostContentAsync(RequestFactory, cancellationToken);

            var result = jresult["upload"].ToObject <UploadResult>(Utility.WikiJsonSerializer);

            site.Logger?.Info(site, $"Upload[{link.Title}]: {result}.");
            switch (result.ResultCode)
            {
            case UploadResultCode.Warning:
                throw new UploadException(result);

            default:
                // UploadResult.Result setter should have thrown an exception.
                Debug.Assert(result.ResultCode == UploadResultCode.Success ||
                             result.ResultCode == UploadResultCode.Continue);
                break;
            }
            return(result);
        }
Example #6
0
 /// <summary>
 /// Asynchronously uploads a file.
 /// </summary>
 /// <param name="site">MedaiWiki site.</param>
 /// <param name="content">Content of the file.</param>
 /// <param name="title">Title of the file, with or without File: prefix.</param>
 /// <param name="comment">Comment of the upload, as well as the page content if it doesn't exist.</param>
 /// <param name="ignoreWarnings">Ignore any warnings. This must be set to upload a new version of an existing image.</param>
 /// <param name="watch">Whether to add the file into your watchlist.</param>
 /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned task.</param>
 /// <exception cref="UploadException">
 /// There's warning from server, and <paramref name="ignoreWarnings"/> is <c>false</c>.
 /// Check <see cref="UploadException.UploadResult"/> for the warning message or continuing the upload.
 /// </exception>
 /// <exception cref="OperationFailedException"> There's an error while uploading the file. </exception>
 /// <exception cref="TimeoutException">
 /// Timeout specified in <see cref="WikiClientBase.Timeout"/> has been reached. Note in this
 /// invocation, there will be no retries.
 /// </exception>
 /// <returns>An <see cref="UploadResult"/>.</returns>
 public static Task <UploadResult> UploadAsync(WikiSite site, Stream content, string title,
                                               string comment, bool ignoreWarnings, AutoWatchBehavior watch, CancellationToken cancellationToken)
 {
     return(UploadAsyncInternal(site, content, title, comment, ignoreWarnings, watch, cancellationToken));
 }
 /// <inheritdoc cref="UploadAsync(WikiSite,string,WikiUploadSource,string,bool,AutoWatchBehavior,CancellationToken)"/>
 public static Task <UploadResult> UploadAsync(this WikiSite site, string title, WikiUploadSource source, string comment, bool ignoreWarnings,
                                               AutoWatchBehavior watch)
 {
     return(UploadAsync(site, title, source, comment, ignoreWarnings, watch, CancellationToken.None));
 }
Example #8
0
 /// <summary>
 /// Deletes the current page.
 /// </summary>
 public Task <bool> DeleteAsync(string reason, AutoWatchBehavior watch)
 {
     return(DeleteAsync(reason, watch, CancellationToken.None));
 }
Example #9
0
        /// <summary>
        /// Moves (renames) a page. (MediaWiki 1.12)
        /// </summary>
        public async Task MoveAsync(string newTitle, string reason, PageMovingOptions options, AutoWatchBehavior watch,
                                    CancellationToken cancellationToken)
        {
            if (newTitle == null)
            {
                throw new ArgumentNullException(nameof(newTitle));
            }
            if (newTitle == Title)
            {
                return;
            }
            using (Site.BeginActionScope(this))
                using (await Site.ModificationThrottler.QueueWorkAsync("Move: " + this, cancellationToken))
                {
                    // When passing this to the Edit API, always pass the token parameter last
                    // (or at least after the text parameter). That way, if the edit gets interrupted,
                    // the token won't be passed and the edit will fail.
                    // This is done automatically by mw.Api.
                    JToken jresult;
                    try
                    {
                        jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new
                        {
                            action = "move",
                            token = WikiSiteToken.Move,
                            from = PageStub.HasTitle ? PageStub.Title : null,
                            fromid = PageStub.HasTitle ? null : (int?)PageStub.Id,
                            to = newTitle,
                            maxlag = 5,
                            movetalk = (options & PageMovingOptions.LeaveTalk) != PageMovingOptions.LeaveTalk,
                            movesubpages = (options & PageMovingOptions.MoveSubpages) == PageMovingOptions.MoveSubpages,
                            noredirect = (options & PageMovingOptions.NoRedirect) == PageMovingOptions.NoRedirect,
                            ignorewarnings = (options & PageMovingOptions.IgnoreWarnings) ==
                                             PageMovingOptions.IgnoreWarnings,
                            watchlist = watch,
                            reason = reason,
                        }), cancellationToken);
                    }
                    catch (OperationFailedException ex)
                    {
                        switch (ex.ErrorCode)
                        {
                        case "cantmove":
                        case "protectedpage":
                        case "protectedtitle":
                            throw new UnauthorizedOperationException(ex);

                        default:
                            if (ex.ErrorCode.StartsWith("cantmove"))
                            {
                                throw new UnauthorizedOperationException(ex);
                            }
                            throw;
                        }
                    }
                    var fromTitle = (string)jresult["move"]["from"];
                    var toTitle   = (string)jresult["move"]["to"];
                    Site.Logger.LogInformation("Page [[{fromTitle}]] has been moved to [[{toTitle}]].", fromTitle, toTitle);
                    var link = WikiLink.Parse(Site, toTitle);
                    PageStub = new WikiPageStub(PageStub.Id, toTitle, link.Namespace.Id);
                }
        }
Example #10
0
        /// <summary>
        /// Submits content contained in <see cref="Content"/>, making edit to the page.
        /// (MediaWiki 1.16)
        /// </summary>
        /// <param name="summary">The edit summary. Leave it blank to use the default edit summary.</param>
        /// <param name="minor">Whether the edit is a minor edit. (See <a href="https://meta.wikimedia.org/wiki/Help:Minor_edit">m:Help:Minor Edit</a>)</param>
        /// <param name="bot">Whether to mark the edit as bot; even if you are using a bot account the edits will not be marked unless you set this flag.</param>
        /// <param name="watch">Specify how the watchlist is affected by this edit.</param>
        /// <param name="cancellationToken">A token used to cancel the operation.</param>
        /// <returns><c>true</c> if page content has been changed; <c>false</c> otherwise.</returns>
        /// <remarks>
        /// This action will refill <see cref="Id" />, <see cref="Title"/>,
        /// <see cref="ContentModel"/>, <see cref="LastRevisionId"/>, and invalidate
        /// <see cref="ContentLength"/>, <see cref="LastRevision"/>, and <see cref="LastTouched"/>.
        /// You should call <see cref="RefreshAsync()"/> again if you're interested in them.
        /// </remarks>
        /// <exception cref="InvalidOperationException">Cannot create actual page in the specified namespace.</exception>
        /// <exception cref="OperationConflictException">Edit conflict detected.</exception>
        /// <exception cref="UnauthorizedOperationException">You have no rights to edit the page.</exception>
        public async Task <bool> UpdateContentAsync(string summary, bool minor, bool bot, AutoWatchBehavior watch,
                                                    CancellationToken cancellationToken)
        {
            using (Site.BeginActionScope(this))
                using (await Site.ModificationThrottler.QueueWorkAsync("Edit: " + this, cancellationToken))
                {
                    // When passing this to the Edit API, always pass the token parameter last
                    // (or at least after the text parameter). That way, if the edit gets interrupted,
                    // the token won't be passed and the edit will fail.
                    // This is done automatically by mw.Api.
                    JToken jresult;
                    try
                    {
                        jresult = await Site.InvokeMediaWikiApiAsync(new MediaWikiFormRequestMessage(new
                        {
                            action = "edit",
                            token = WikiSiteToken.Edit,
                            title = PageStub.HasTitle ? PageStub.Title : null,
                            pageid = PageStub.HasTitle ? null : (int?)PageStub.Id,
                            minor = minor,
                            bot = bot,
                            recreate = true,
                            maxlag = 5,
                            basetimestamp = LastRevision?.TimeStamp,
                            watchlist = watch,
                            summary = summary,
                            text = Content,
                        }), cancellationToken);
                    }
                    catch (OperationFailedException ex)
                    {
                        switch (ex.ErrorCode)
                        {
                        case "protectedpage":
                            throw new UnauthorizedOperationException(ex);

                        case "pagecannotexist":
                            throw new InvalidOperationException(ex.ErrorMessage, ex);

                        default:
                            throw;
                        }
                    }
                    var jedit  = jresult["edit"];
                    var result = (string)jedit["result"];
                    if (result == "Success")
                    {
                        if (jedit["nochange"] != null)
                        {
                            Site.Logger.LogInformation("Submitted empty edit to page.");
                            return(false);
                        }
                        ContentModel   = (string)jedit["contentmodel"];
                        LastRevisionId = (int)jedit["newrevid"];
                        // jedit["ns"] == null
                        PageStub = new WikiPageStub((int)jedit["pageid"], (string)jedit["title"], PageStub.NamespaceId);
                        Site.Logger.LogInformation("Edited page. New revid={RevisionId}.", LastRevisionId);
                        return(true);
                    }
                    // No "errors" in json result but result is not Success.
                    throw new OperationFailedException(result, (string)null);
                }
        }
Example #11
0
 /// <inheritdoc cref="UpdateContentAsync(string,bool,bool,AutoWatchBehavior,CancellationToken)"/>
 public Task <bool> UpdateContentAsync(string summary, bool minor, bool bot, AutoWatchBehavior watch)
 {
     return(UpdateContentAsync(summary, minor, bot, watch, new CancellationToken()));
 }
Example #12
0
        /// <summary>
        /// Moves (renames) a page. (MediaWiki 1.12)
        /// </summary>
        public async Task MoveAsync(string newTitle, string reason, PageMovingOptions options, AutoWatchBehavior watch,
                                    CancellationToken cancellationToken)
        {
            if (newTitle == null)
            {
                throw new ArgumentNullException(nameof(newTitle));
            }
            if (newTitle == Title)
            {
                return;
            }
            using (await Site.ModificationThrottler.QueueWorkAsync("Move: " + this, cancellationToken))
            {
                var token = await Site.GetTokenAsync("move", cancellationToken);

                // When passing this to the Edit API, always pass the token parameter last
                // (or at least after the text parameter). That way, if the edit gets interrupted,
                // the token won't be passed and the edit will fail.
                // This is done automatically by mw.Api.
                JToken jresult;
                try
                {
                    jresult = await Site.PostValuesAsync(new
                    {
                        action         = "move",
                        from           = Title,
                        to             = newTitle,
                        maxlag         = 5,
                        movetalk       = (options & PageMovingOptions.LeaveTalk) != PageMovingOptions.LeaveTalk,
                        movesubpages   = (options & PageMovingOptions.MoveSubpages) == PageMovingOptions.MoveSubpages,
                        noredirect     = (options & PageMovingOptions.NoRedirect) == PageMovingOptions.NoRedirect,
                        ignorewarnings = (options & PageMovingOptions.IgnoreWarnings) ==
                                         PageMovingOptions.IgnoreWarnings,
                        watchlist = watch,
                        reason    = reason,
                        token     = token,
                    }, cancellationToken);
                }
                catch (OperationFailedException ex)
                {
                    switch (ex.ErrorCode)
                    {
                    case "cantmove":
                    case "protectedpage":
                    case "protectedtitle":
                        throw new UnauthorizedOperationException(ex);

                    default:
                        if (ex.ErrorCode.StartsWith("cantmove"))
                        {
                            throw new UnauthorizedOperationException(ex);
                        }
                        throw;
                    }
                }
                var fromTitle = (string)jresult["move"]["from"];
                var toTitle   = (string)jresult["move"]["to"];
                Site.Logger?.Info(this, $"Page {fromTitle} has been moved to {toTitle} .");
                Title = toTitle;
            }
        }
Example #13
0
        /// <summary>
        /// Submits content contained in <see cref="Content"/>, making edit to the page.
        /// (MediaWiki 1.16)
        /// </summary>
        /// <returns><c>true</c> if page content has been changed; <c>false</c> otherwise.</returns>
        /// <remarks>
        /// This action will refill <see cref="Id" />, <see cref="Title"/>,
        /// <see cref="ContentModel"/>, <see cref="LastRevisionId"/>, and invalidate
        /// <see cref="ContentLength"/>, <see cref="LastRevision"/>, and <see cref="LastTouched"/>.
        /// You should call <see cref="RefreshAsync()"/> again
        /// if you're interested in them.
        /// </remarks>
        /// <exception cref="InvalidOperationException">Cannot create actual page in the specified namespace.</exception>
        /// <exception cref="OperationConflictException">Edit conflict detected.</exception>
        /// <exception cref="UnauthorizedOperationException">You have no rights to edit the page.</exception>
        public async Task <bool> UpdateContentAsync(string summary, bool minor, bool bot, AutoWatchBehavior watch,
                                                    CancellationToken cancellationToken)
        {
            using (await Site.ModificationThrottler.QueueWorkAsync("Edit: " + this, cancellationToken))
            {
                var token = await Site.GetTokenAsync("edit", cancellationToken);

                // When passing this to the Edit API, always pass the token parameter last
                // (or at least after the text parameter). That way, if the edit gets interrupted,
                // the token won't be passed and the edit will fail.
                // This is done automatically by mw.Api.
                JToken jresult;
                try
                {
                    jresult = await Site.PostValuesAsync(new
                    {
                        action        = "edit",
                        title         = Title,
                        minor         = minor,
                        bot           = bot,
                        recreate      = true,
                        maxlag        = 5,
                        basetimestamp = LastRevision?.TimeStamp,
                        watchlist     = watch,
                        summary       = summary,
                        text          = Content,
                        token         = token,
                    }, cancellationToken);
                }
                catch (OperationFailedException ex)
                {
                    switch (ex.ErrorCode)
                    {
                    case "protectedpage":
                        throw new UnauthorizedOperationException(ex);

                    case "pagecannotexist":
                        throw new InvalidOperationException(ex.ErrorMessage, ex);

                    default:
                        throw;
                    }
                }
                var jedit  = jresult["edit"];
                var result = (string)jedit["result"];
                if (result == "Success")
                {
                    if (jedit["nochange"] != null)
                    {
                        return(false);
                    }
                    ContentModel   = (string)jedit["contentmodel"];
                    LastRevisionId = (int)jedit["newrevid"];
                    Id             = (int)jedit["pageid"];
                    Title          = (string)jedit["title"];
                    return(true);
                }
                // No "errors" in json result but result is not Success.
                throw new OperationFailedException(result, (string)null);
            }
        }