🐙 GitHub - 📦 npm - 📘 API Documentation
In code examples in this guide and the API documentation, Mwbot
refers to the class, while the lowercase mwbot
refers to an instance of the class.
mwbot-ts is a MediaWiki bot framework for Node.js that works with both JavaScript and TypeScript. It offers a highly customizable core class and a bot-friendly wikitext parser for common operations. Portions of the source code are adapted from mwbot, mwn, types-mediawiki, and MediaWiki core.
Main features:
OAuth 2.0
, OAuth 1.0a
, and BotPasswords
. Anonymous access is also supported for non-write requests.{"formatversion": "2"}
is enabled by default). For full control, the rawRequest
method is also available.maxlag
parameter for server load (default: 5 seconds), and retrying failed HTTP requests under appropriate conditions. See also mw:API:Data formats and mw:API:Etiquette.postWithToken
or postWithCsrfToken
, which closely mirror the functionality of MediaWiki’s built-in mediawiki.Api
module.action=edit
, action=move
, and action=upload
requests, relieving clients of manual throttling.Mwbot
, which is designed to be subclassed — making it easy to implement shared logic, custom workflows, or application-specific behavior.To install the package, run:
npm install mwbot-ts
mwbot-ts
exports three primary values (along with several types and interfaces):
MWBOT_VERSION
- A constant that stores the package version.To create a new Mwbot
instance, use the static method Mwbot.init. This method serves as the entry point for all initialization processes: It sets the API endpoint, defines default request parameters, validates login credentials or OAuth consumer secrets, fetches the site's metadata, and prepares all site-info-dependent auxiliary classes.
import { Mwbot } from 'mwbot-ts';
Mwbot.init(initOptions, defaultRequestOptions).then((mwbot) => {
// Use `mwbot` for your purposes...
});
The Mwbot.init
method requires a MwbotInitOptions object, which consists of MwbotOptions and Credentials. This object is used to initialize default instance options and authentication credentials:
import { Mwbot, MwbotInitOptions } from 'mwbot-ts';
const initOptions: MwbotInitOptions = {
/**
* The API endpoint. This property is required.
*/
apiUrl: 'https://en.wikipedia.org/w/api.php', // Required
/**
* User agent for the application. Typed as optional, but should always be included
* per https://www.mediawiki.org/wiki/API:Etiquette#The_User-Agent_header
*/
userAgent: 'MyCoolBot/1.0.0 (https://github.com/Foo/MyCoolBot)', // Example
/**
* Default interval (in milliseconds) between specific actions.
*/
interval: 5000, // Defaults to 5 seconds; optional
/**
* API actions between which to enforce an interval.
*/
intervalActions: ['edit', 'move', 'upload'], // Defaults to these 3; optional
/**
* Whether to suppress warnings returned by the API.
*/
suppressWarnings: false, // Defaults to false
// One of the following must be provided for authentification:
credentials: {
oAuth2AccessToken: 'Your OAuth 2.0 access token'
},
credentials: {
consumerToken: 'Your OAuth 1.0a consumer token',
consumerSecret: 'Your OAuth 1.0a consumer secret',
accessToken: 'Your OAuth 1.0a access token',
accessSecret: 'Your OAuth 1.0a access secret'
},
credentials: {
username: 'Your bot username',
password: 'Your bot password'
},
credentials: {
anonymous: true // For anonymous access; the client will be limited to non-write requests
}
};
Mwbot.init(initOptions).then((mwbot) => {
// Use `mwbot` for your purposes...
});
For full flexibility, MwbotOptions
can later be updated using the setMwbotOptions method. However, altering apiUrl
after initialization is not recommended, as mwbot
instances depend on site-specific metadata. As a best practice, limit the use of this method to updating interval-related options, and consider creating a new instance instead.
The second argument of Mwbot.init
is a MwbotRequestConfig object, which is an extension of the Axios request config. If provided, this configuration will be applied to every HTTP request made using the instance. See also Mwbot.defaultRequestOptions for default settings.
In general, there is no need to set custom request configurations. The following options, in particular, should not be altered unless you know exactly what you’re doing:
(Properties of MwbotRequestConfig
)
method
- Use GET
for read-only requests whenever possible. POST
is not cacheable and may be routed to a distant datacenter in multi-datacenter setups (such as Wikimedia sites).headers['Content-Type']
- The MediaWiki API only supports application/x-www-form-urlencoded
and multipart/form-data
. The framework selects the appropriate content type automatically.headers['Accept-Encoding']
- Handles data compression to optimize bandwidth usage.params.format
- Should always be 'json'
, as all other formats have been deprecated or removed. mwbot-ts
enforces this by throwing an error if the specification is missing.params.formatversion
- The framework assumes {"formatversion": "2"}
to define types and interfaces. Changing this breaks type expectations and offers no benefit.responseType
- Should be left unchanged, as with params.format
; otherwise, it may cause an invalidjson
error.responseEncoding
- Modifying this may cause garbled text unless handled carefully.For basic API calls, mwbot-ts
provides the get
and post
methods, both built on the method-neutral request
method. For read-only queries with long parameters, the nonwritePost
method acts as a POST-based alternative to get
, helping avoid 414 URI Too Long
errors. When you’re unsure whether to use GET
or POST
for a read-only query, the fetch
method automatically selects the appropriate method based on request length and intent. For complete control over the request configuration, use rawRequest
. To cancel all in-flight requests, use abort
.
// Use for standard GET-based queries (read-only, short parameters)
mwbot.get({
action: 'query',
titles: 'Wikipedia:Sandbox',
prop: 'info',
format: 'json',
formatversion: '2'
})
.then(console.log);
// Use for standard write operations or when POST is required
mwbot.post({
action: 'purge',
titles: 'Wikipedia:Sandbox',
format: 'json',
formatversion: '2'
})
.then(console.log);
// Use when a GET request may exceed URL length limits
mwbot.nonwritePost({
action: 'query',
list: 'blocks',
bkusers: [/* very long field */], // Note: `mwbot-ts` accepts array inputs
bklimit: 'max',
format: 'json',
formatversion: '2'
})
.then(console.log);
// Performs a read-only API request, letting mwbot-ts decide the appropriate HTTP method
// Automatically switches from GET to POST if the query is too long to fit in the URL
mwbot.fetch({
action: 'query',
titles: [/* A field that can be either short or long */],
format: 'json',
formatversion: '2'
})
.then(console.log);
// Performs a raw HTTP GET request to an arbitrary external API
const res = await mwbot.rawRequest({
method: 'GET',
url: 'https://api.github.com/repos/nodejs/node',
headers: {
'User-Agent': `mwbot-ts/${MWBOT_VERSION} (https://github.com/Dr4goniez/mwbot-ts)`
},
timeout: 10000,
responseType: 'json'
});
console.log(res.data.full_name); // "nodejs/node"
// Cancels all ongoing HTTP requests issued by this instance
mwbot.abort();
The continuedRequest method simplifies handling API continuation. It returns a Promise
that resolves to an array of continued API responses:
// Performs an API request with automatic continuation
// By default, continues up to 10 times (or until complete)
mwbot.continuedRequest({
action: 'query',
list: 'logevents',
leprop: 'ids|title|timestamp',
letype: 'newusers',
lelimit: 'max',
format: 'json',
formatversion: '2'
})
.then(console.log);
The massRequest method makes it easier to work with API parameters that accept multiple values. It automatically splits long fields into batches and bundles the results for you (see also the apilimit getter):
// Performs batched API requests when a multi-value field exceeds the limit
// Automatically splits the field and bundles the responses
mwbot.massRequest({
action: 'query',
list: 'blocks',
bkprop: 'user|timestamp|expiry|restrictions|flags',
bklimit: 'max',
bkusers: [/* very long field */],
format: 'json',
formatversion: '2'
}, 'bkusers')
.then(console.log);
When working with the MediaWiki API, handling tokens for database write actions can be tedious: you need to manage cookies and sessions, fetch a token in one request, and include it in a subsequent write request — while ensuring the request method and encoding are correct. The postWithToken method automates this process. It caches tokens for you and appends the required token
property to your request parameters:
// Example for using a "watch" token
mwbot.postWithToken('watch', {
action: 'watch',
titles: 'Wikipedia:Sandbox',
format: 'json',
formatversion: '2',
// `token` parameter automatically appended
})
.then(console.log);
See also postWithCsrfToken, a shorthand method for the commonly used csrf
token.
mwbot-ts
provides the following four methods for editing pages:
Promise
is rejected with an articleexists
error. There's no need to pre-check for existence.mwbot.edit()
also supports exclusion compliance — that is, reading {{bots}}
and {{nobots}}
templates in the page content and determining whether your bot is opted in. With mwbot-ts
, this is easy to implement: simply pass { comply: true }
to the method. There’s no need to handle any complex regex manually. See also ExclusionComplianceConfig
.All of these methods return a Promise
that resolves with an ApiResponseEditSuccess object or rejects with a MwbotError
(see #Error handling). When the Promise
resolves, the result
field is always 'Success'
, and all other cases are handled with a rejection. In other words, there is no need to inspect the response object to verify whether the edit succeeded: When then()
is called, that's a success, and when catch()
is called, that's a failure.
{
new: true,
result: 'Success', // This will NEVER be anything but 'Success'
pageid: 165499,
title: 'Bar',
contentmodel: 'wikitext',
oldrevid: 0,
newrevid: 654607,
newtimestamp: '2025-04-15T11:32:49Z',
watched: true
}
// Creates a page titled 'Bar'
mwbot.create(
'Bar', // Page title
'* Test. --~~~~', // Page content
'test' // Optional edit summary
)
.then(console.log);
// Edits an existing page titled 'Foo' and replaces its entire content
mwbot.save(
'Foo', // The page title
'* Test. --~~~~', // The page content
'test' // Optional edit summary
)
.then(console.log);
// Adds a new section to page 'Foo'
mwbot.newSection(
'Foo', // The page title
'Bot notice', // The section title
'Hi. ~~~~', // The section content
'Bot: Notification' // Optional edit summary
)
.then(console.log);
// Edits 'Bar' by transforming the existing content
mwbot.edit(
'Bar',
(wikitext, revision) => {
const newContent = wikitext.content + '\n* Test. --~~~~';
return { text: newContent }; // Parameters to `action=edit`
}
)
.then(console.log);
To handle edit failures, all you need to do is:
import { MwbotError } from 'mwbot-ts';
// ...
const response = await mwbot.save(...args).catch((err: MwbotError) => err);
if (response instanceof MwbotError) {
if (response.code !== 'aborted') {
console.error(response); // MwbotError object
}
} else {
console.log(response); // ApiResponseEditSuccess
}
mwbot-ts
also provides helper methods for common tasks, as listed below. Versatile utility methods may be added upon request, and such requests are always welcome! 😊
block
: Blocks a user.delete
: Deletes a page.getBacklinks
: Retrieves a list of pages that link to the given page(s).getCategories
: Retrieves the categories to which the specified titles belong.getCategoriesByPrefix
: Returns a list of categories that match a given prefix.getCategoryMembers
: Retrieves a list of pages that belong to the given category.getExistencePredicate
: Returns an exists()
function that checks whether given pages exist.getTransclusions
: Retrieves a list of pages that transclude the given page(s).move
: Moves a page.parse
: Runs the parser via the API.prefixSearch
: Performs a prefix search for page titles.protect
: Protects a page.purge
: Clears the server-side cache for the specified pages.read
: Retrieves the latest revision content of the specified page(s).rollback
: Rolls back the most recent edits to a page made by a specific user.search
: Performs a full-text search.unblock
: Unblocks a user.undelete
: Undeletes revisions of a deleted page.unprotect
: Unprotects a page.Different bot operators have different needs, and it’s common to define custom functions using native framework methods. For example, a quick way to check whether a page exists might look like this:
Note:
mwbot-ts
includes a built-in method for checking page existence:getExistencePredicate
.
exists()
functionconst exists = async (title: string): Promise<boolean> => {
// mwbot.read() rejects if the page doesn't exist
const res = await mwbot.read(title).catch(() => false);
return !!res; // true if the page exists; otherwise false
};
This works, but it comes with a few limitations:
mwbot
instance, especially if the function lives in a separate module.mwbot.read()
fetches the entire page content, which is overkill if you only want to check existence.A more scalable and idiomatic solution is to extend the Mwbot
class. Subclassing lets you define custom behavior as instance methods while preserving access to all built-in functionality and internal configuration. You avoid wiring up external helpers and instead encapsulate logic cleanly within the class.
exists()
methodimport { Mwbot } from 'mwbot-ts';
class Mwbot2 extends Mwbot {
/**
* Checks if the given page exists. Returns `null` if the request fails.
* @param title
*/
exists(title: string): Promise<boolean | null> {
return this.get({
action: 'query',
titles: title,
format: 'json',
formatversion: '2'
}).then((res) => {
const page = res.query?.pages?.[0];
return page ? !page.missing : null;
}).catch(() => null);
}
}
Mwbot2.init(initOptions).then(async (mwbot) => {
// ✓ Fully type-safe, no TypeScript errors
const sandExists = await mwbot.exists('Wikipedia:Sandbox');
console.log(sandExists);
// ✓ Superclass methods and properties are still accessible
console.log(mwbot.config.get('wgNamespaceIds'));
});
Note: The
exists()
method above is intended as a simple example. In production, it should also account for false positives. For instance, if the input title is invalid, the response may lack amissing
property, leading this method to incorrectly returntrue
(see this example).
Extending the class also allows you to optimize or customize default request behavior. For example, if you'd like your purge()
call to include the forcerecursivelinkupdate
parameter by default, you can define your own specialized method:
purgeDeep()
methodclass Mwbot2 extends Mwbot {
purgeDeep(
titles: (string | Title)[],
additionalParams: ApiParams = {},
requestOptions: MwbotRequestConfig = {}
) {
additionalParams = Object.assign(
{ forcerecursivelinkupdate: true },
additionalParams
);
return this.purge(titles, additionalParams, requestOptions);
}
}
Tip: When extending the class, always choose unique method names. Avoid overriding built-in methods, as some rely on others internally.
If you're using TypeScript, it's highly recommended to enable thenoImplicitOverride
option in yourtsconfig.json
. This ensures you’ll get a compile-time error if you accidentally reuse a name that already exists, or is added in a version update.
mwbot-ts
uses a custom error class named MwbotError to standardize error handling. When Mwbot's request methods reject with an error, they always return an instance of MwbotError
with a full stack trace.
This design addresses a key shortcoming of the original mwbot
framework — that is, it was often unclear what properties an error object would contain. In contrast, MwbotError
instances always include a code
and info
property, closely mirroring the error structure used by MediaWiki’s built-in mediawiki.Api
module when returning API error responses.
mwbot.post({
action: 'edit',
title: 'Wikipedia:Sandbox',
appendtext: '\n* Test. --~~~~',
format: 'json',
formatversion: '2'
})
.catch(console.error);
MwbotError: The "token" parameter must be set.
at Function.newFromResponse (W:\Programming\Git\home\mwbot-ts\src\MwbotError.ts:119:10)
at Mwbot._request (W:\Programming\Git\home\mwbot-ts\src\Mwbot.ts:1062:27)
at processTicksAndRejections (node:internal/process/task_queues:95:5) {
type: 'api',
code: 'missingparam',
info: 'The "token" parameter must be set.',
data: {
error: {
docref: 'See https://ja.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.'
}
}
}
The type
property categorizes the error source:
'api'
: An error originating directly from the MediaWiki API.'api_mwbot'
: An error defined internally by mwbot-ts
, such as generic HTTP errors returned from Axios.'fatal'
: A logic or implementation error occurring within the client itself.Given this consistent structure, a single catch
block can gracefully handle virtually any error:
Promise.prototype.catch
:import { MwbotError } from 'mwbot-ts';
// ...
const response = await mwbot.request(params).catch((err: MwbotError) => err);
if (response instanceof MwbotError) {
if (response.code !== 'aborted') {
console.error(response); // MwbotError object
}
} else {
console.log(response); // JSON response
}
For a complete list of possible error codes, refer to the MwbotErrorCodes documentation.
The Wikitext class, accesible via mwbot.Wikitext
, facilitates common bot operations involving wikitext parsing. It currently supports five types of wiki markup:
<tag></tag>
== section ==
{{{parameter}}}
{{template}}
, including (double-braced) magic words and parser functions.[[wikilink]]
For each markup type, the class provides parse**
and modify**
methods, where **
denotes the type (e.g., parseTemplates
).
To create a new instance, use the constructor or the static newFromTitle method, which fetches the content of a page and returns a Promise
resolving to a Wikitext
instance:
Wikitext.constructor
const wikitext = new mwbot.Wikitext('some wikitext');
Wikitext.newFromTitle
const wikitext = await mwbot.Wikitext.newFromTitle('Wikipedia:Sandbox');
The parsing and modification methods are available as instance methods:
wikitext.parseTags
const text =
'My name is <b>Foo</b><!-- not "Bar"! -->.\n' +
'You can ping me using <nowiki>{{PingFoo}}</nowiki>.';
const wikitext = new mwbot.Wikitext(text);
console.log(wikitext.parseTags());
[
[Object: null prototype] {
name: 'b',
text: [Getter],
start: '<b>',
content: 'Foo',
end: '</b>',
startIndex: 11,
endIndex: 21,
nestLevel: 0,
void: false,
unclosed: false,
selfClosing: false,
skip: false,
index: 0,
parent: null,
children: Set(0) {}
},
[Object: null prototype] {
name: '!--',
text: [Getter],
start: '<!--',
content: ' not "Bar"! ',
end: '-->',
startIndex: 21,
endIndex: 40,
nestLevel: 0,
void: false,
unclosed: false,
selfClosing: false,
skip: false,
index: 1,
parent: null,
children: Set(0) {}
},
[Object: null prototype] {
name: 'nowiki',
text: [Getter],
start: '<nowiki>',
content: '{{PingFoo}}',
end: '</nowiki>',
startIndex: 64,
endIndex: 92,
nestLevel: 0,
void: false,
unclosed: false,
selfClosing: false,
skip: false,
index: 2,
parent: null,
children: Set(0) {}
}
]
wikitext.parseSections
const text =
'== Foo ==\n' +
'=== Bar ===\n' +
'[[Main page]]\n' +
'== Baz ==\n' +
'[[Another page]]';
const wikitext = new mwbot.Wikitext(text);
console.log(wikitext.parseSections());
[
[Object: null prototype] {
heading: '',
title: 'top',
level: 1,
index: 0,
startIndex: 0,
endIndex: 0,
content: '',
text: [Getter],
parent: null,
children: Set(0) {}
},
[Object: null prototype] {
heading: '== Foo ==\n',
title: 'Foo',
level: 2,
index: 1,
startIndex: 0,
endIndex: 36,
content: '=== Bar ===\n[[Main page]]\n',
text: [Getter],
parent: null,
children: Set(1) { 2 }
},
[Object: null prototype] {
heading: '=== Bar ===\n',
title: 'Bar',
level: 3,
index: 2,
startIndex: 10,
endIndex: 36,
content: '[[Main page]]\n',
text: [Getter],
parent: 1,
children: Set(0) {}
},
[Object: null prototype] {
heading: '== Baz ==\n',
title: 'Baz',
level: 2,
index: 3,
startIndex: 36,
endIndex: 62,
content: '[[Another page]]',
text: [Getter],
parent: null,
children: Set(0) {}
}
]
wikitext.parseParameters
const text =
'{{#if:{{{1|}}}\n' +
'<!--|{{{type}}}-->' +
'|{{{1}}}\n' +
'|{{{2|empty}}}\n' +
'}}';
const wikitext = new mwbot.Wikitext(text);
console.log(wikitext.parseParameters());
[
[Object: null prototype] {
key: '1',
value: '',
text: '{{{1|}}}',
index: 0,
startIndex: 6,
endIndex: 14,
nestLevel: 0,
skip: false,
parent: null,
children: Set(0) {}
},
[Object: null prototype] {
key: 'type',
value: null,
text: '{{{type}}}',
index: 1,
startIndex: 20,
endIndex: 30,
nestLevel: 0,
skip: true,
parent: null,
children: Set(0) {}
},
[Object: null prototype] {
key: '1',
value: null,
text: '{{{1}}}',
index: 2,
startIndex: 34,
endIndex: 41,
nestLevel: 0,
skip: false,
parent: null,
children: Set(0) {}
},
[Object: null prototype] {
key: '2',
value: 'empty',
text: '{{{2|empty}}}',
index: 3,
startIndex: 43,
endIndex: 56,
nestLevel: 0,
skip: false,
parent: null,
children: Set(0) {}
}
]
wikitext.parseTemplates
const text =
'{{Foo\n' +
'|anchor={{#if:{{ns:0}}|Foo|Bar}}\n' +
'|logo=[[File:Img.png|thumb|Mr. [[Foo]]|300px]]\n' +
'}}';
const wikitext = new mwbot.Wikitext(text);
console.dir(wikitext.parseTemplates(), {depth: null});
[
ParsedTemplate {
title: Title {
namespace: 10,
title: 'Foo',
fragment: null,
colon: '',
interwiki: '',
local_interwiki: false
},
params: [Object: null prototype] {
anchor: {
key: 'anchor',
value: '{{#if:{{ns:0}}|Foo|Bar}}',
text: [Getter],
unnamed: false,
duplicates: []
},
logo: {
key: 'logo',
value: '[[File:Img.png|thumb|Mr. [[Foo]]|300px]]',
text: [Getter],
unnamed: false,
duplicates: []
}
},
rawTitle: 'Foo\n',
text: '{{Foo\n' +
'|anchor={{#if:{{ns:0}}|Foo|Bar}}\n' +
'|logo=[[File:Img.png|thumb|Mr. [[Foo]]|300px]]\n' +
'}}',
index: 0,
startIndex: 0,
endIndex: 88,
nestLevel: 0,
skip: false,
parent: null,
children: Set(1) { 1 }
},
ParsedParserFunction {
params: [ '{{ns:0}}', 'Foo', 'Bar' ],
hook: '#if:',
canonicalHook: '#if:',
rawHook: '#if:',
text: '{{#if:{{ns:0}}|Foo|Bar}}',
index: 1,
startIndex: 14,
endIndex: 38,
nestLevel: 1,
skip: false,
parent: 0,
children: Set(2) { 1, 2 }
},
ParsedParserFunction {
params: [ '0' ],
hook: 'ns:',
canonicalHook: 'ns:',
rawHook: 'ns:',
text: '{{ns:0}}',
index: 2,
startIndex: 20,
endIndex: 28,
nestLevel: 2,
skip: false,
parent: 1,
children: Set(0) {}
}
]
wikitext.parseWikilinks
const text =
'[[File:Img.png|thumb|right|300px]]\n' +
"'''Foo''' is a [[metasyntactic variable]]<!--[[metavariable]]-->.";
const wikitext = new mwbot.Wikitext(text);
console.dir(wikitext.parseWikilinks(), {depth: null});
[
ParsedFileWikilink {
params: [ 'thumb', 'right', '300px' ],
title: Title {
namespace: 6,
title: 'Img.png',
fragment: null,
colon: '',
interwiki: '',
local_interwiki: false
},
rawTitle: 'File:Img.png',
text: '[[File:Img.png|thumb|right|300px]]',
index: 0,
startIndex: 0,
endIndex: 34,
nestLevel: 0,
skip: false,
parent: null,
children: Set(0) {}
},
ParsedWikilink {
title: Title {
namespace: 0,
title: 'Metasyntactic_variable',
fragment: null,
colon: '',
interwiki: '',
local_interwiki: false
},
rawTitle: 'metasyntactic variable',
text: '[[metasyntactic variable]]',
index: 1,
startIndex: 50,
endIndex: 76,
nestLevel: 0,
skip: false,
parent: null,
children: Set(0) {}
},
ParsedWikilink {
title: Title {
namespace: 0,
title: 'Metavariable',
fragment: null,
colon: '',
interwiki: '',
local_interwiki: false
},
rawTitle: 'metavariable',
text: '[[metavariable]]',
index: 2,
startIndex: 80,
endIndex: 96,
nestLevel: 0,
skip: true,
parent: null,
children: Set(0) {}
}
]
In many cases, you can skip calling the parsing methods:
modify**
methods directly, which internally call the appropriate parse**
method and apply a transformation via a callback function. Each callback receives one element from the parsed array.mwbot.edit()
(referred to as "transformation predicate"), which takes a Wikitext
instance as its first argument.wikitext.modifyTags
// Example: Remove empty <noinclude> tags from "Wikipedia:Sandbox"
const result = await mwbot.edit('Wikipedia:Sandbox', (wikitext) => {
const oldContent = wikitext.content;
const newContent = wikitext.modifyTags((tag, _i, _arr) => {
const isEmptyNoinclude =
tag.name === 'noinclude' &&
// Ignore e.g., <nowiki><noinclude></noinclude></nowiki>
!tag.skip &&
// Ignore <noinclude />
!tag.selfClosing &&
// Eliminate uncertainty
!tag.unclosed &&
// Has no content or only whitespace
!tag.content?.trim();
// A string return indicates "replace `tag.text` with this value";
// `null` means no change.
return isEmptyNoinclude ? '' : null;
});
if (oldContent === newContent) {
// A tranformation predicate returning `null` will cancel the edit
// via a Promise rejection with an "aborted" error code
console.log('Edit cancelled.');
return null;
}
// Return API parameters for `action=edit`
// Partial specification is acceptable because `edit()` provides default parameters
return { text: newContent };
}).catch((err: MwbotError) => err);
// Uniform error handling
if (result instanceof MwbotError) {
if (result.code !== 'aborted') {
console.error(result); // Failure
}
} else {
console.log(result); // Success
}
wikitext.modifySections
// Example: Insert {{Section resolved}} to the beginning of second-level sections
const text =
'== Foo ==<!--\n-->\n' +
'* Foo. --~~~~\n' +
'=== Foo2 ===\n' +
'* Foo2. --~~~~\n' +
'== Bar ==\n' +
'* Bar. --~~~~\n' +
'== Baz ==\n' +
'* Baz. --~~~~';
const wikitext = new mwbot.Wikitext(text);
const content = wikitext.modifySections((section) => {
// Do not modify sections that are not level 2
if (section.level !== 2) {
return null;
}
// Check whether the section heading ends with a newline; if not, add one
const newline = section.heading.endsWith('\n') ? '' : '\n';
// Insert the template between the heading and the content
return section.heading + newline + '{{Section resolved|1=~~~~}}\n' + section.content;
});
console.log(content);
== Foo ==<!--
-->
{{Section resolved|1=~~~~}}
* Foo. --~~~~
=== Foo2 ===
* Foo2. --~~~~
== Bar ==
{{Section resolved|1=~~~~}}
* Bar. --~~~~
== Baz ==
{{Section resolved|1=~~~~}}
* Baz. --~~~~
wikitext.modifyParameters
// Rename {{{1}}} to {{{User}}}
const text =
'{{#if:{{{1|}}}\n' +
'|{{{1}}}\n' +
'|{{{Ip|Foo}}}\n' +
'}}';
const wikitext = new mwbot.Wikitext(text);
const content = wikitext.modifyParameters((parameter) => {
if (parameter.key === '1' && !parameter.skip) {
const value = parameter.value !== null ? '|' + parameter.value : '';
return '{{{User' + value + '}}}';
} else {
return null;
}
});
console.log(content);
{{#if:{{{User|}}}
|{{{User}}}
|{{{Ip|Foo}}}
}}
wikitext.modifyTemplates
// Add "|done" to {{Status}} and leave a bot comment
const text =
'=== Global lock for Foo ===\n' +
'{{Status}}\n' +
'*{{LockHide|1=Foo}}\n' +
'Long-term abuse. [[User:Bar|Bar]] ([[User talk:Bar|talk]]) 00:00, 1 January 2025 (UTC)';
const wikitext = new mwbot.Wikitext(text);
// Modify {{Status}}
let status: ParsedTemplate | null = null;
let content = wikitext.modifyTemplates((template) => {
if (status) {
return null;
}
const isStatus =
mwbot.Template.is(template, 'ParsedTemplate') &&
template.title.getPrefixedDb() === 'Template:Status' &&
!template.skip;
if (isStatus) {
status = template;
return template
.insertParam('1', 'done', true, 'end')
.stringify({suppressKeys: ['1']});
}
return null;
});
if (status === null) {
return;
}
// Leave a bot comment
const containingSection = wikitext.identifySection(status);
if (!containingSection) {
return;
}
content = wikitext.modifySections((section) => {
if (section.index === containingSection.index) {
return section.text.trim() + "\n: '''Robot clerk note:''' {{done}} by Baz. ~~~~\n";
}
return null;
});
console.log(content);
=== Global lock for Foo ===
{{Status|done}}
*{{LockHide|1=Foo}}
Long-term abuse. [[User:Bar|Bar]] ([[User talk:Bar|talk]]) 00:00, 1 January 2025 (UTC)
: '''Robot clerk note:''' {{done}} by Baz. ~~~~
wikitext.modifyWikilinks
// Replace all instances of [[Category:Foo]] with [[Category:Bar]]
const text =
'This category belongs to [[:Category:Foo]].\n' +
'[[Category:Foo|*]]';
const wikitext = new mwbot.Wikitext(text);
const NS_CATEGORY = mwbot.config.get('wgNamespaceIds').category;
const content = wikitext.modifyWikilinks((link) => {
const isFoo =
mwbot.Wikilink.is(link, 'ParsedWikilink') &&
link.title.getNamespaceId() === NS_CATEGORY && link.title.getMain() === 'Foo' &&
!link.skip;
if (isFoo) {
const colon = link.title.hadLeadingColon() ? ':' : '';
link.setTitle(colon + 'Category:Bar'); // Preserve the leading colon, if present
return link.stringify();
}
return null;
});
console.log(content);
This category belongs to [[:Category:Bar]].
[[Category:Bar|*]]
When a modifying markup has another markup (of the same type) nested within it, modifications can end up overwriting changes made to the parent markup. To handle this safely, use the context
object, which is passed as the fourth argument to the ModificationPredicate
:
// Showcase 1
const text = '{{Foo|1={{Bar}}}}';
const wikitext = new mwbot.Wikitext(text);
const newContent = wikitext.modifyTemplates((temp, i) => {
if (i === 0) {
return (temp as ParsedTemplate).insertParam('1', 'My name is Foo.').stringify();
// Replaces the entire first parameter of {{Foo}} with "My name is Foo.",
// removing the original nested {{Bar}} entirely.
// Result: {{Foo|1=My name is Foo.}}
} else {
return (temp as ParsedTemplate).insertParam('1', 'bar').stringify();
// Modifies the inner {{Bar}} template by inserting 1=bar.
// Result: {{Bar|1=bar}}
// Then, this modified {{Bar}} is reinserted into {{Foo}} as its first parameter.
// Because the original {{Bar}} and the string "My name" are both 7 characters long,
// the replacement keeps the trailing " is Foo." from the previous call.
// So, the final output is:
// => {{Foo|1={{Bar|1=bar}} is Foo.}}
}
});
console.log(newContent); // {{Foo|1={{Bar|1=bar}} is Foo.}}
// Showcase 2: Demonstrating parent-child template modification with context awareness
const text = '{{Foo|1={{Bar}}}}';
const wikitext = new mwbot.Wikitext(text);
const newContent = wikitext.modifyTemplates((temp, i, _arr, context) => {
if (i === 0) {
return (temp as ParsedTemplate).insertParam('1', 'My name is Foo.').stringify();
// This modifies the outer {{Foo}} template by replacing its first parameter value.
// The original value `{{Bar}}` is entirely replaced with the string "My name is Foo."
// => {{Foo|1=My name is Foo.}}
// As a result, the inner {{Bar}} is no longer present in the final wikitext.
// Therefore, there's no point in modifying {{Bar}} separately.
}
// Only modify {{Bar}} if it was not overwritten by its parent.
else if (!context.touched) {
return (temp as ParsedTemplate).insertParam('1', 'bar').stringify();
// This would modify {{Bar}} into {{Bar|1=bar}}, but in this case it won't happen,
// because the parent {{Foo}} was already modified and discarded the original {{Bar}}.
}
return null; // No change
});
console.log(newContent); // Output: {{Foo|1=My name is Foo.}}
A class that parses MediaWiki page titles into an object structure, adapted and expanded from the native mediawiki.Title
module.
This extended class is accessible via mwbot.Title (an instance member), and provides support for parsing interwiki titles — a feature exclusive to mwbot-ts
.
Mirrors the native mw.config
, which provides access to MediaWiki's wg
-configuration variables. While site and user information initialized by Mwbot.init
can be accessed via mwbot.info
, mwbot.config
further simplifies handling this data by relieving clients of the burden of normalizing the relevant objects.
mwbot.config
const config = mwbot.config;
console.log(config.get('wgFormattedNamespaces'));
console.log(config.get('wgUserRights')); // This key is exclusive to mwbot-ts
See ConfigData for available keys.
A class that exports the framework's internal utility functions for use by the client, accessible via Mwbot.Util
(a static member).
This class should not be confused with the native mediawiki.util
module. Note that mwbot-ts
uses the npm package ip-wiki
for IP string normalization. Mwbot.Util
does not handle this on its own. Since the package is registered as a dependency, you do not need to install it separately; you can directly require or import ip-wiki
for manipulating IP and CIDR addresses.
A class that provides functions for string manipulation, accessible via Mwbot.String
(a static member). It is a copy of the native mediawiki.String
module, included to support the functionality of mwbot.Title
.
{{double-braced}}
wiki markups can be constructed as objects using mwbot.Template
and mwbot.ParserFunction
, which are lazy-loaded instance members.
mwbot.Template
: Parses {{template}}
markups into an object structure.mwbot.ParserFunction
: Parses {{#function}}
markups into an object structure.[[double-bracketed]]
wiki markups can be constructed as objects using mwbot.Wikilink
and mwbot.FileWikilink
, which are lazy-loaded instance members.
mwbot.Wikilink
: Parses non-file [[wikilink]]
markups into an object structure.mwbot.FileWikilink
: Parses file [[wikilink]]
markups into an object structure.