Socket
Socket
Sign inDemoInstall

mwn

Package Overview
Dependencies
35
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.7.0 to 0.7.1

6

package.json
{
"name": "mwn",
"version": "0.7.0",
"description": "MediaWiki bot framework for NodeJS",
"version": "0.7.1",
"description": "MediaWiki bot framework for Node.js",
"main": "./src/bot.js",

@@ -12,3 +12,3 @@ "scripts": {

"type": "git",
"url": "github.com/siddharthvp/mwn"
"url": "https://github.com/siddharthvp/mwn"
},

@@ -15,0 +15,0 @@ "keywords": [

# mwn
[![NPM version](https://img.shields.io/npm/v/mwn.svg)](https://www.npmjs.com/package/mwn)
**mwn** is a modern MediaWiki bot framework in NodeJS, orginally adapted from [mwbot](https://github.com/Fannon/mwbot).
Development status: **Unstable**. Versioning: while mwn is in version 0, changes may be made to the public interface with a change in the minor version number.
Development status: Unstable. Versioning: while mwn is in version 0, changes may be made to the public interface with a change in the minor version number.

@@ -38,6 +39,11 @@ Documentation given below is incomplete. There are a number of additional classes such as `bot.title`, `bot.wikitext`, `bot.page`, etc that provide useful functionality but aren't documented.

#### Set up a bot password
#### MediaWiki version
mwn is written for and tested on the latest version of MediaWiki used on WMF wikis.
To be able to login to the wiki, you have to set up a bot password using the wiki's [Special:BotPasswords](https://en.wikipedia.org/wiki/Special:BotPasswords) page.
#### Set up a bot password or OAuth credentials
mwn supports authentication via both BotPasswords and via OAuth. Use of OAuth is recommended as it does away the need for separate API requests for logging in, and is also a bit more secure.
Bot passwords may be a bit easier to set up. To generate one, go to the wiki's [Special:BotPasswords](https://en.wikipedia.org/wiki/Special:BotPasswords) page.
If you're migrating from mwbot, note that:

@@ -51,14 +57,23 @@ - `edit` in mwbot is different from `edit` in mwn. You want to use `save` instead.

```js
const bot = new mwn();
const bot = new mwn({
apiUrl: 'https://en.wikipedia.org/w/api.php',
username: 'YourBotUsername',
password: 'YourBotPassword'
});
await bot.login();
```
Log in to the bot:
Or to use OAuth:
```js
bot.login({
apiUrl: 'https://en.wikipedia.org/w/api.php',
username: 'YourBotUsername',
password: 'YourBotPassword'
const bot = new mwn({
apiUrl: 'https://en.wikipedia.org/w/api.php',
oauth_consumer_token: "16_DIGIT_ALPHANUMERIC_KEY",
oauth_consumer_secret: "20_DIGIT_ALPHANUMERIC_KEY",
oauth_access_token: "16_DIGIT_ALPHANUMERIC_KEY",
oauth_access_secret: "20_DIGIT_ALPHANUMERIC_KEY"
});
bot.initOAuth(); // does not involve an API call
// Any errors in authentication will surface when the first actual API call is made
```
Set default parameters to be sent to be included in every API request:

@@ -276,2 +291,2 @@ ```js

```
Note that `seriesBatchOperation` with delay=0 is same as `batchOperation` with concurrency=1.
Note that `seriesBatchOperation` with delay=0 is same as `batchOperation` with concurrency=1.

@@ -105,6 +105,6 @@ /**

this.defaultOptions = {
// suppress messages, except for error messages
// suppress messages, except for error messages and warnings
silent: false,
// site API url, example https://en.wikipedia.org/w/api.php
// site API url, example "https://en.wikipedia.org/w/api.php"
apiUrl: null,

@@ -150,2 +150,5 @@

// suppress logging of warnings received from the API
suppressAPIWarnings: false,
// options for the edit() function

@@ -252,3 +255,3 @@ editConfig: {

static async init(config) {
var bot = new mwn(config);
const bot = new mwn(config);
if (bot._usingOAuth()) {

@@ -383,3 +386,3 @@ bot.initOAuth();

if (!requestOptions.url) {
var err = new Error('No URL provided!');
const err = new Error('No URL provided!');
err.disableRetry = true;

@@ -411,3 +414,3 @@ return Promise.reject(err);

var getOrPost = function(data) {
const getOrPost = function (data) {
if (data.action === 'query') {

@@ -433,3 +436,3 @@ return 'get';

const MULTIPART_THRESHOLD = 8000;
var hasLongFields = false;
let hasLongFields = false;

@@ -468,3 +471,3 @@ // pre-process params:

var contentTypeGiven = customRequestOptions.headers &&
const contentTypeGiven = customRequestOptions.headers &&
customRequestOptions.headers['Content-Type'];

@@ -475,3 +478,3 @@

// use multipart/form-data
var form = new formData();
let form = new formData();
for (let [key, val] of Object.entries(params)) {

@@ -601,3 +604,3 @@ if (val.stream) {

if (response.warnings && !this.suppressAPIWarnings) {
if (response.warnings && !this.options.suppressAPIWarnings) {
for (let [key, info] of Object.entries(response.warnings)) {

@@ -629,3 +632,3 @@ log(`[W] Warning received from API: ${key}: ${info.warnings}`);

dieWithError(response, requestOptions) {
var err = new Error(response.error.code + ': ' + response.error.info);
let err = new Error(response.error.code + ': ' + response.error.info);
// Enhance error object with additional information

@@ -854,3 +857,3 @@ err.errorResponse = true;

} catch(e) {
return Promise.reject('invalidjson');
return this.rejectWithErrorCode('invalidjson');
}

@@ -872,3 +875,3 @@ });

*
* @returns {Promise}
* @returns {Promise<{{title: string, revisions: ({content: string})[]}}>}
*/

@@ -884,3 +887,3 @@ read(titles, options) {

typeof titles[0] === 'number' ? 'pageids' : 'titles').then(jsons => {
var data = jsons.reduce((data, json) => {
let data = jsons.reduce((data, json) => {
json.query.pages.forEach(pg => {

@@ -903,3 +906,3 @@ if (pg.revisions) {

prop: 'revisions',
rvprop: 'content',
rvprop: 'content|timestamp',
rvslots: 'main',

@@ -945,29 +948,35 @@ redirects: '1'

var basetimestamp, curtimestamp;
let basetimestamp, curtimestamp;
return this.request(merge({
return this.request({
action: 'query',
...makeTitles(title),
prop: 'revisions',
rvprop: ['content', 'timestamp'],
rvslots: 'main',
formatversion: '2',
curtimestamp: !0
}, makeTitles(title))).then(data => {
var page, revision;
}).then(data => {
let page, revision, revisionContent;
if (!data.query || !data.query.pages) {
return Promise.reject('unknown');
return this.rejectWithErrorCode('unknown');
}
page = data.query.pages[0];
if (!page || page.invalid) {
return Promise.reject('invalidtitle');
return this.rejectWithErrorCode('invalidtitle');
}
if (page.missing) {
return Promise.reject('nocreate-missing');
return this.rejectWithErrorCode('nocreate-missing');
}
revision = page.revisions[0];
try {
revisionContent = revision.slots.main.content;
} catch(err) {
return this.rejectWithErrorCode('unknown');
}
basetimestamp = revision.timestamp;
curtimestamp = data.curtimestamp;
if (editConfig.exclusionRegex && editConfig.exclusionRegex.test(revision.content)) {
return Promise.reject('bot-denied');
if (editConfig.exclusionRegex && editConfig.exclusionRegex.test(revisionContent)) {
return this.rejectWithErrorCode('bot-denied');
}

@@ -977,3 +986,3 @@

timestamp: revision.timestamp,
content: revision.content
content: revisionContent
});

@@ -985,3 +994,3 @@

}
var editParams = typeof returnVal === 'object' ? returnVal : {
const editParams = typeof returnVal === 'object' ? returnVal : {
text: String(returnVal)

@@ -994,3 +1003,4 @@ };

starttimestamp: curtimestamp,
nocreate: !0,
nocreate: 1,
bot: 1,
token: this.csrfToken

@@ -1003,10 +1013,9 @@ }, makeTitle(title), editParams));

}
if (!data.edit) {
log(`[W] Unusual API success response: ` + JSON.stringify(data,undefined,2));
}
return data.edit;
}, errorCode => {
if (errorCode === 'editconflict' && editConfig.conflictRetries > 0) {
}, err => {
if (err.code === 'editconflict' && editConfig.conflictRetries > 0) {
editConfig.conflictRetries--;
return this.edit(title, transform, editConfig);
} else {
return Promise.reject(err);
}

@@ -1031,2 +1040,3 @@ });

summary: summary,
bot: 1,
token: this.csrfToken

@@ -1052,3 +1062,4 @@ }, makeTitle(title), options)).then(data => data.edit);

summary: summary,
createonly: '1',
createonly: 1,
bot: 1,
token: this.csrfToken

@@ -1073,2 +1084,3 @@ }, options)).then(data => data.edit);

text: message,
bot: 1,
token: this.csrfToken

@@ -1198,3 +1210,3 @@ }, makeTitle(title), additionalParams)).then(data => data.edit);

if (data.upload.warnings) {
log('[W] The API returned warnings while uploading to ' + title + ':');
log(`[W] The API returned warnings while uploading to ${title}:`);
log(data.upload.warnings);

@@ -1250,4 +1262,4 @@ }

}, makeTitles(file))).then(data => {
var url = data.query.pages[0].imageinfo[0].url;
var name = new this.title(data.query.pages[0].title).getMainText();
const url = data.query.pages[0].imageinfo[0].url;
const name = new this.title(data.query.pages[0].title).getMainText();
return this.downloadFromUrl(url, localname || name);

@@ -1313,3 +1325,3 @@ });

getPagesByPrefix(prefix, otherParams) {
var title = Title.newFromText(prefix);
const title = Title.newFromText(prefix);
if (!title) {

@@ -1336,3 +1348,3 @@ throw new Error('invalid prefix for getPagesByPrefix');

getPagesInCategory(category, otherParams) {
var title = Title.newFromText(category, 14);
const title = Title.newFromText(category, 14);
return this.request(merge({

@@ -1361,3 +1373,3 @@ "action": "query",

srlimit: limit,
srprop: props || 'size|worcount|timestamp',
srprop: props || 'size|wordcount|timestamp',
}, otherParams)).then(data => {

@@ -1379,4 +1391,4 @@ return data.query.search;

continuedQuery(query, limit=10) {
var responses = [];
var callApi = (query, count) => {
let responses = [];
let callApi = (query, count) => {
return this.request(query).then(response => {

@@ -1446,6 +1458,6 @@ if (!this.options.silent) {

massQuery(query, batchFieldName='titles') {
var batchValues = query[batchFieldName];
var limit = this.options.hasApiHighLimit ? 500 : 50;
var numBatches = Math.ceil(batchValues.length / limit);
var batches = new Array(numBatches);
let batchValues = query[batchFieldName];
const limit = this.options.hasApiHighLimit ? 500 : 50;
const numBatches = Math.ceil(batchValues.length / limit);
let batches = new Array(numBatches);
for (let i = 0; i < numBatches - 1; i++) {

@@ -1458,5 +1470,5 @@ batches[i] = new Array(limit);

}
var responses = new Array(numBatches);
let responses = new Array(numBatches);
return new Promise((resolve) => {
var sendQuery = (idx) => {
const sendQuery = (idx) => {
if (idx === numBatches) {

@@ -1471,3 +1483,3 @@ return resolve(responses);

throw new Error(`[mwn] Your account doesn't have apihighlimit right.` +
` Set the option hasApiHighLimit as false`);
` Set the option hasApiHighLimit as false`);
}

@@ -1489,6 +1501,6 @@ responses[idx] = err;

async *massQueryGen(query, batchFieldName='titles') {
var batchValues = query[batchFieldName];
var limit = this.options.hasApiHighLimit ? 500 : 50;
var batches = arrayChunk(batchValues, limit);
var numBatches = batches.length;
let batchValues = query[batchFieldName];
const limit = this.options.hasApiHighLimit ? 500 : 50;
const batches = arrayChunk(batchValues, limit);
const numBatches = batches.length;

@@ -1517,18 +1529,18 @@ for (let i = 0; i < numBatches; i++) {

batchOperation(list, worker, concurrency=5, retries=0) {
var counts = {
let counts = {
successes: 0,
failures: 0
};
var failures = [];
var incrementSuccesses = () => {
let failures = [];
let incrementSuccesses = () => {
counts.successes++;
};
var incrementFailures = (idx) => {
const incrementFailures = (idx) => {
counts.failures++;
failures.push(list[idx]);
};
var updateStatusText = () => {
var percentageFinished = Math.round((counts.successes + counts.failures) / list.length * 100);
var percentageSuccesses = Math.round(counts.successes / (counts.successes + counts.failures) * 100);
var statusText = `[+] Finished ${counts.successes + counts.failures}/${list.length} (${percentageFinished}%) tasks, of which ${counts.successes} (${percentageSuccesses}%) were successful, and ${counts.failures} failed.`;
const updateStatusText = () => {
const percentageFinished = Math.round((counts.successes + counts.failures) / list.length * 100);
const percentageSuccesses = Math.round(counts.successes / (counts.successes + counts.failures) * 100);
const statusText = `[+] Finished ${counts.successes + counts.failures}/${list.length} (${percentageFinished}%) tasks, of which ${counts.successes} (${percentageSuccesses}%) were successful, and ${counts.failures} failed.`;
if (!this.options.silent) {

@@ -1538,6 +1550,6 @@ log(statusText);

};
var numBatches = Math.ceil(list.length / concurrency);
const numBatches = Math.ceil(list.length / concurrency);
return new Promise((resolve) => {
var sendBatch = (batchIdx) => {
const sendBatch = (batchIdx) => {

@@ -1547,4 +1559,4 @@ // Last batch

var numItemsInLastBatch = list.length - batchIdx * concurrency;
var finalBatchPromises = new Array(numItemsInLastBatch);
const numItemsInLastBatch = list.length - batchIdx * concurrency;
const finalBatchPromises = new Array(numItemsInLastBatch);

@@ -1555,3 +1567,3 @@ // Hack: Promise.allSettled requires NodeJS 12.9+

// finalBatchPromises are resolved or rejected.
var finalBatchSettledPromises = new Array(numItemsInLastBatch);
let finalBatchSettledPromises = new Array(numItemsInLastBatch);

@@ -1586,3 +1598,3 @@ for (let i = 0; i < numItemsInLastBatch; i++) {

var promise = worker(list[idx], idx);
const promise = worker(list[idx], idx);
if (!ispromise(promise)) {

@@ -1617,18 +1629,18 @@ throw new Error('batchOperation worker function must return a promise');

seriesBatchOperation(list, worker, delay=5000, retries=0) {
var counts = {
let counts = {
successes: 0,
failures: 0
};
var failures = [];
var incrementSuccesses = () => {
let failures = [];
const incrementSuccesses = () => {
counts.successes++;
};
var incrementFailures = (idx) => {
const incrementFailures = (idx) => {
counts.failures++;
failures.push(list[idx]);
};
var updateStatusText = () => {
var percentageFinished = Math.round((counts.successes + counts.failures) / list.length * 100);
var percentageSuccesses = Math.round(counts.successes / (counts.successes + counts.failures) * 100);
var statusText = `[+] Finished ${counts.successes + counts.failures}/${list.length} (${percentageFinished}%) tasks, of which ${counts.successes} (${percentageSuccesses}%) were successful, and ${counts.failures} failed.`;
const updateStatusText = () => {
const percentageFinished = Math.round((counts.successes + counts.failures) / list.length * 100);
const percentageSuccesses = Math.round(counts.successes / (counts.successes + counts.failures) * 100);
const statusText = `[+] Finished ${counts.successes + counts.failures}/${list.length} (${percentageFinished}%) tasks, of which ${counts.successes} (${percentageSuccesses}%) were successful, and ${counts.failures} failed.`;
if (!this.options.silent) {

@@ -1640,3 +1652,3 @@ log(statusText);

return new Promise((resolve) => {
var trigger = (idx) => {
const trigger = (idx) => {
if (list[idx] === undefined) { // reached the end

@@ -1649,3 +1661,3 @@ if (counts.failures !== 0 && retries > 0) {

}
var promise = worker(list[idx], idx);
const promise = worker(list[idx], idx);
if (!ispromise(promise)) {

@@ -1733,5 +1745,5 @@ throw new Error('seriesBatchOperation worker function must return a promise');

oresQueryRevisions(endpointUrl, models, revisions) {
var response = {};
var chunks = arrayChunk(
(revisions instanceof Array) ? revisions : [ revisions ],
let response = {};
const chunks = arrayChunk(
(revisions instanceof Array) ? revisions : [revisions],
50

@@ -1824,3 +1836,3 @@ );

}).sort((a, b) => {
a.bytes < b.bytes ? 1 : -1;
return a.bytes < b.bytes ? 1 : -1;
});

@@ -1841,2 +1853,14 @@

/**
* Returns a promise rejected with an error object
* @private
* @param {string} errorcode
* @returns {Promise<Error>}
*/
rejectWithErrorCode(errorcode) {
let error = new Error(errorcode);
error.code = errorcode;
return Promise.reject(error);
}
}

@@ -1867,3 +1891,3 @@

/** Check whether object looks like a promises-A+ promise, from https://www.npmjs.com/package/is-promise */
var ispromise = function (obj) {
const ispromise = function (obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') &&

@@ -1874,3 +1898,3 @@ typeof obj.then === 'function';

/** Check whether an object is plain object, from https://github.com/sindresorhus/is-plain-obj/blob/master/index.js */
var isplainobject = function(value) {
const isplainobject = function (value) {
if (Object.prototype.toString.call(value) !== '[object Object]') {

@@ -1883,3 +1907,2 @@ return false;

/**

@@ -1893,3 +1916,3 @@ * Simple wrapper around Object.assign to merge objects. null and undefined

*/
var merge = function(...objects) {
const merge = function(...objects) {
// {} used as first parameter as this object is mutated by default

@@ -1907,3 +1930,3 @@ return Object.assign({}, ...objects);

*/
var mergeDeep1 = function(...objects) {
const mergeDeep1 = function(...objects) {
let args = [...objects].filter(e => e); // skip null/undefined values

@@ -1925,5 +1948,5 @@ for (let options of args.slice(1)) {

/** @param {Array} arr, @param {number} size */
var arrayChunk = function(arr, size) {
var numChunks = Math.ceil(arr.length / size);
var result = new Array(numChunks);
const arrayChunk = function(arr, size) {
const numChunks = Math.ceil(arr.length / size);
let result = new Array(numChunks);
for (let i=0; i<numChunks; i++) {

@@ -1935,3 +1958,3 @@ result[i] = arr.slice(i * size, (i + 1) * size);

var makeTitles = function(pages) {
const makeTitles = function(pages) {
pages = Array.isArray(pages) ? pages : [ pages ];

@@ -1946,3 +1969,3 @@ if (typeof pages[0] === 'number') {

var makeTitle = function(page) {
const makeTitle = function(page) {
if (typeof page === 'number') {

@@ -1949,0 +1972,0 @@ return { pageid: page };

@@ -222,2 +222,13 @@ module.exports = function(bot) {

// Tweak set* methods (setHours, setUTCMinutes, etc) so that they
// return the modified xdate object rather than the seconds-since-epoch
// representation which is what JS Date() gives
Object.getOwnPropertyNames(Date.prototype).filter(f => f.startsWith('set')).forEach(func => {
let proxy = xdate.prototype[func];
xdate.prototype[func] = function(...args) {
proxy.call(this, ...args);
return this;
};
});
xdate.localeData = {

@@ -224,0 +235,0 @@ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],

@@ -100,3 +100,3 @@ module.exports = function(bot) {

if (page.missing) {
return Promise.reject('missingarticle');
return bot.rejectWithErrorCode('missingarticle');
}

@@ -122,3 +122,3 @@ return page.linkshere.map(pg => pg.title);

if (page.missing) {
return Promise.reject('missingarticle');
return bot.rejectWithErrorCode('missingarticle');
}

@@ -200,3 +200,3 @@ return page.transcludedin.map(pg => pg.title);

if (page.missing) {
return Promise.reject('missingarticle');
return bot.rejectWithErrorCode('missingarticle');
}

@@ -223,3 +223,3 @@ return page.title;

if (page.missing) {
return Promise.reject('missingarticle');
return bot.rejectWithErrorCode('missingarticle');
}

@@ -250,2 +250,7 @@ return page.revisions[0].user;

/**
* Get short description, either the local one (for English Wikipedia)
* or the one from wikidata.
* @param {Object} customOptions
*/
getDescription(customOptions) {

@@ -260,3 +265,3 @@ return bot.request({

if (page.missing) {
return Promise.reject('missingarticle');
return bot.rejectWithErrorCode('missingarticle');
}

@@ -287,3 +292,3 @@ return data.query.pages[0].description;

if (page.missing) {
return Promise.reject('missingarticle');
return bot.rejectWithErrorCode('missingarticle');
}

@@ -290,0 +295,0 @@ return data.query.pages[0].revisions;

@@ -6,2 +6,6 @@ /**

/*
* Definitions of some private functions used
*/
var rawurlencode = function( str ) {

@@ -8,0 +12,0 @@ return encodeURIComponent( String( str ) )

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc