Comparing version 0.6.0 to 0.7.0
{ | ||
"name": "mwn", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"description": "MediaWiki bot framework for NodeJS", | ||
@@ -29,2 +29,3 @@ "main": "./src/bot.js", | ||
"axios-cookiejar-support": "^1.0.0", | ||
"eventsource": "^1.0.7", | ||
"form-data": "^3.0.0", | ||
@@ -45,2 +46,2 @@ "oauth-1.0a": "^2.2.6", | ||
} | ||
} | ||
} |
136
src/bot.js
@@ -51,2 +51,3 @@ /** | ||
const Title = require('./title'); | ||
const Xdate = require('./date'); | ||
const Page = require('./page'); | ||
@@ -57,2 +58,3 @@ const Wikitext = require('./wikitext'); | ||
const File = require('./file'); | ||
const Stream = require('./eventstream'); | ||
const static_utils = require('./static_utils'); | ||
@@ -204,2 +206,7 @@ | ||
/** | ||
* Date class | ||
*/ | ||
this.date = Xdate(this); | ||
/** | ||
* Page class associated with bot instance | ||
@@ -229,2 +236,7 @@ */ | ||
/** | ||
* Sub-class for the EventStreams API | ||
*/ | ||
this.stream = Stream(mwn, this); | ||
// set up any semlog options | ||
@@ -554,3 +566,3 @@ semlog.updateConfig(this.options.semlog || {}); | ||
log(`[W] Encountered ${response.error.code} error, waiting for ${this.options.retryPause/1000} seconds before retrying`); | ||
return sleep(this.options.retryPause).then(() => { | ||
return this.sleep(this.options.retryPause).then(() => { | ||
return this.request(params, customRequestOptions); | ||
@@ -586,2 +598,8 @@ }); | ||
if (response.warnings && !this.suppressAPIWarnings) { | ||
for (let [key, info] of Object.entries(response.warnings)) { | ||
log(`[W] Warning received from API: ${key}: ${info.warnings}`); | ||
} | ||
} | ||
return response; | ||
@@ -595,3 +613,3 @@ | ||
customRequestOptions.retryNumber = requestOptions.retryNumber + 1; | ||
return sleep(this.options.retryPause).then(() => { | ||
return this.sleep(this.options.retryPause).then(() => { | ||
return this.request(params, customRequestOptions); | ||
@@ -841,4 +859,8 @@ }); | ||
/** | ||
* Reads the content / and meta-data of one (or many) pages | ||
* Reads the content and and meta-data of one (or many) pages. | ||
* Content from the "main" slot is copied over to every revision object | ||
* for easier referencing (`pg.revisions[0].content` can be used instead of | ||
* `pg.revisions[0].slots.main.content`). | ||
* | ||
* | ||
* @param {string|string[]|number|number[]} titles - for multiple pages use an array | ||
@@ -854,2 +876,3 @@ * @param {Object} [options] | ||
rvprop: 'content|timestamp', | ||
rvslots: 'main', | ||
redirects: '1' | ||
@@ -859,2 +882,9 @@ }, makeTitles(titles), options), | ||
var data = jsons.reduce((data, json) => { | ||
json.query.pages.forEach(pg => { | ||
if (pg.revisions) { | ||
pg.revisions.forEach(rev => { | ||
Object.assign(rev, rev.slots.main); | ||
}); | ||
} | ||
}); | ||
return data.concat(json.query.pages); | ||
@@ -871,2 +901,3 @@ }, []); | ||
rvprop: 'content', | ||
rvslots: 'main', | ||
redirects: '1' | ||
@@ -879,2 +910,7 @@ }, makeTitles(titles), options), | ||
for (let pg of response.query.pages) { | ||
if (pg.revisions) { | ||
pg.revisions.forEach(rev => { | ||
Object.assign(rev, rev.slots.main); | ||
}); | ||
} | ||
yield pg; | ||
@@ -1691,2 +1727,86 @@ } | ||
/** | ||
* Query the top contributors to the article using the WikiWho API. | ||
* This API has a throttling of 2000 requests a day. | ||
* Supported for EN, DE, ES, EU, TR Wikipedias only | ||
* @see https://api.wikiwho.net/ | ||
* @param {string} title | ||
* @returns {{totalBytes: number, users: ({id: number, name: string, bytes: number, percent: number})[]}} | ||
*/ | ||
async queryAuthors(title) { | ||
let langcodematch = this.options.apiUrl.match(/([^/]*?)\.wikipedia\.org/); | ||
if (!langcodematch || !langcodematch[1]) { | ||
throw new Error('WikiWho API is not supported for bot API url. Re-check.'); | ||
} | ||
let json; | ||
try { | ||
json = await this.rawRequest({ | ||
url: `https://api.wikiwho.net/${langcodematch[1]}/api/v1.0.0-beta/latest_rev_content/${encodeURIComponent(title)}/?editor=true` | ||
}); | ||
} catch(err) { | ||
throw new Error(err && err.response && err.response.data | ||
&& err.response.data.Error); | ||
} | ||
const tokens = Object.values(json.revisions[0])[0].tokens; | ||
let data = { | ||
totalBytes: 0, | ||
users: [] | ||
}, userdata = {}; | ||
for (let token of tokens) { | ||
data.totalBytes += token.str.length; | ||
let editor = token['editor']; | ||
if (!userdata[editor]) { | ||
userdata[editor] = { bytes: 0 }; | ||
} | ||
userdata[editor].bytes += token.str.length; | ||
if (editor.startsWith('0|')) { // IP | ||
userdata[editor].name = editor.slice(2); | ||
} | ||
} | ||
Object.entries(userdata).map(([userid, {bytes}]) => { | ||
userdata[userid].percent = bytes / data.totalBytes; | ||
if (userdata[userid].percent < 0.02) { | ||
delete userdata[userid]; | ||
} | ||
}); | ||
await this.request({ | ||
"action": "query", | ||
"list": "users", | ||
"ususerids": Object.keys(userdata).filter(us => !us.startsWith('0|')) // don't lookup IPs | ||
}).then(json => { | ||
json.query.users.forEach(us => { | ||
userdata[String(us.userid)].name = us.name; | ||
}); | ||
}); | ||
data.users = Object.entries(userdata).map(([userid, {bytes, name, percent}]) => { | ||
return { | ||
id: userid, | ||
name: name, | ||
bytes: bytes, | ||
percent: percent | ||
}; | ||
}).sort((a, b) => { | ||
a.bytes < b.bytes ? 1 : -1; | ||
}); | ||
return data; | ||
} | ||
/** | ||
* Promisified version of setTimeout | ||
* @param {number} duration - of sleep in milliseconds | ||
*/ | ||
sleep(duration) { | ||
return new Promise(resolve => { | ||
setTimeout(resolve, duration); | ||
}); | ||
} | ||
} | ||
@@ -1779,12 +1899,2 @@ | ||
/** | ||
* Promisified version of setTimeout | ||
* @param {number} duration - of sleep in milliseconds | ||
*/ | ||
var sleep = function(duration) { | ||
return new Promise(resolve => { | ||
setTimeout(resolve, duration); | ||
}); | ||
}; | ||
var makeTitles = function(pages) { | ||
@@ -1791,0 +1901,0 @@ pages = Array.isArray(pages) ? pages : [ pages ]; |
@@ -106,3 +106,24 @@ module.exports = function(bot) { | ||
/** | ||
* Get list of pages transcluding this page | ||
* @returns {Promise<String[]>} | ||
*/ | ||
transclusions() { | ||
return bot.continuedQuery({ | ||
"action": "query", | ||
"prop": "transcludedin", | ||
"titles": this.toString(), | ||
"tiprop": "title", | ||
"tilimit": "max" | ||
}).then(jsons => { | ||
var pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
var page = pages[0]; | ||
if (page.missing) { | ||
return Promise.reject('missingarticle'); | ||
} | ||
return page.transcludedin.map(pg => pg.title); | ||
}); | ||
} | ||
/** | ||
@@ -206,2 +227,6 @@ * Returns list of images on the page | ||
/** | ||
* Get username of the last deleting admin (or null) | ||
* @returns {Promise<string>} | ||
*/ | ||
getDeletingAdmin() { | ||
@@ -223,2 +248,17 @@ return bot.request({ | ||
getDescription(customOptions) { | ||
return bot.request({ | ||
action: 'query', | ||
prop: 'description', | ||
titles: this.toString(), | ||
...customOptions | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
if (page.missing) { | ||
return Promise.reject('missingarticle'); | ||
} | ||
return data.query.pages[0].description; | ||
}); | ||
} | ||
/** | ||
@@ -225,0 +265,0 @@ * Get the edit history of the page |
@@ -134,6 +134,6 @@ /** | ||
_getheaderline(header) { | ||
if (typeof header === 'object') { | ||
var text = `scope="col"`; | ||
for (let [key, value] of Object.entries(header)) { | ||
_makecell(cell, isHeader) { | ||
if (typeof cell === 'object') { | ||
let text = isHeader ? `scope="col"` : ``; | ||
for (let [key, value] of Object.entries(cell)) { | ||
if (key === 'label') { | ||
@@ -144,6 +144,6 @@ continue; | ||
} | ||
text += ` | ${header.label}`; | ||
text += ` | ${cell.label}`; | ||
return text; | ||
} | ||
return header; | ||
return cell; | ||
} | ||
@@ -157,5 +157,5 @@ /** | ||
if (this.multiline) { | ||
this.text += headers.map(e => `! ${this._getheaderline(e)} \n`).join(''); | ||
this.text += headers.map(e => `! ${this._makecell(e, true)} \n`).join(''); | ||
} else { | ||
this.text += `! ` + headers.map(this._getheaderline).join(' !! ') + '\n'; | ||
this.text += `! ` + headers.map(e => this._makecell(e, true)).join(' !! ') + '\n'; | ||
} | ||
@@ -176,5 +176,5 @@ } | ||
if (this.multiline) { | ||
this.text += fields.map(e => `| ${e} \n`).join(''); | ||
this.text += fields.map(e => `| ${this._makecell(e)} \n`).join(''); | ||
} else { | ||
this.text += `| ` + fields.join(' || ') + '\n'; | ||
this.text += `| ` + fields.map(this._makecell).join(' || ') + '\n'; | ||
} | ||
@@ -181,0 +181,0 @@ } |
@@ -13,2 +13,5 @@ module.exports = function(bot) { | ||
/** | ||
* @returns {bot.page} | ||
*/ | ||
get userpage() { | ||
@@ -20,2 +23,5 @@ // User: namespace name will work across all MW sites | ||
/** | ||
* @returns {bot.page} | ||
*/ | ||
get talkpage() { | ||
@@ -72,2 +78,17 @@ return new bot.page('User talk:' + this.username); | ||
/** | ||
* Get global user info for wikis with CentralAuth | ||
* @param {("groups"|"rights"|"merged"|"unattached"|"editcount")[]} props | ||
*/ | ||
globalinfo(props) { | ||
return bot.request({ | ||
"action": "query", | ||
"meta": "globaluserinfo", | ||
"guiuser": this.username, | ||
"guiprop": props || '' | ||
}).then(data => { | ||
return data.query.globaluserinfo; | ||
}); | ||
} | ||
/** | ||
* Post a message on user's talk page | ||
@@ -74,0 +95,0 @@ * @param {string} header |
@@ -5,3 +5,4 @@ module.exports = function (bot) { | ||
* Class for some basic wikitext parsing, involving | ||
* links, files, categories, templates and simple tables. | ||
* links, files, categories, templates and simple tables | ||
* and sections. | ||
* | ||
@@ -25,8 +26,2 @@ * For more advanced and sophisticated wikitext parsing, use | ||
this.text = wikitext; | ||
this.unbinder = { | ||
counter: 0, | ||
history: {}, | ||
prefix: '%UNIQ::' + Math.random() + '::', | ||
postfix: '::UNIQ%' | ||
}; | ||
} | ||
@@ -40,8 +35,10 @@ | ||
var n = this.text.length; | ||
let n = this.text.length; | ||
// files can have links in captions; use a stack to handle the nesting | ||
var stack = new Stack(); | ||
let stack = new Stack(); | ||
for (let i = 0; i < n; i++) { | ||
if (this.text[i] === '[' && this.text[i + 1] === '[') { | ||
stack.push({ startIdx: i }); | ||
stack.push({ | ||
startIdx: i | ||
}); | ||
i++; | ||
@@ -74,15 +71,119 @@ } else if (this.text[i] === ']' && this.text[i + 1] === ']' && stack.top()) { | ||
* count: number}} config | ||
* @config {boolean} recursive - also parse templates within subtemplates | ||
* @config {function} namePredicate - include template in result only if the its name matches this predicate | ||
* More efficient than templatePredicate as the template parameters aren't parsed if name didn't match. | ||
* @config {function} templatePredicate - include template in result only if it matches this predicate | ||
* @config {number} count - max number of templates to be parsed. If recursive is set true, note that | ||
* templates are parsed breadth-first, not depth-first. | ||
* @config {boolean} recursive - also parse templates within subtemplates. The other | ||
* config parameters (namePredicate, templatePredicate, count) are *not* compatible | ||
* with recursive mode. Expect unexpected results if used. | ||
* @config {function} namePredicate - include template in result only if the its name | ||
* matches this predicate. More efficient than templatePredicate as the template parameters | ||
* aren't parsed if name didn't match. | ||
* @config {function} templatePredicate - include template in result only if it matches | ||
* this predicate | ||
* @config {number} count - max number of templates to be parsed. | ||
* @returns {Template[]} | ||
*/ | ||
parseTemplates(config) { | ||
return this.templates = parseTemplates(this.text, config); | ||
return this.templates = Wikitext.parseTemplates(this.text, config); | ||
} | ||
// parseTemplates() and processTemplateText() are adapted from | ||
// https://en.wikipedia.org/wiki/MediaWiki:Gadget-libExtraUtil.js written by Evad37 | ||
// which was in turn adapted from https://en.wikipedia.org/wiki/User:SD0001/parseAllTemplates.js | ||
// written by me. (cc-by-sa/GFDL) | ||
/** | ||
* @inheritdoc | ||
*/ | ||
static parseTemplates(wikitext, config) { | ||
config = config || { | ||
recursive: false, | ||
namePredicate: null, | ||
templatePredicate: null, | ||
count: null | ||
}; | ||
const result = []; | ||
const n = wikitext.length; | ||
// number of unclosed braces | ||
let numUnclosed = 0; | ||
// are we inside a comment, or between nowiki tags, or in a {{{parameter}}}? | ||
let inComment = false; | ||
let inNowiki = false; | ||
let inParameter = false; | ||
let startIdx, endIdx; | ||
for (let i = 0; i < n; i++) { | ||
if (!inComment && !inNowiki && !inParameter) { | ||
if (wikitext[i] === '{' && wikitext[i + 1] === '{' && wikitext[i + 2] === '{' && wikitext[i + 3] !== '{') { | ||
inParameter = true; | ||
i += 2; | ||
} else if (wikitext[i] === '{' && wikitext[i + 1] === '{') { | ||
if (numUnclosed === 0) { | ||
startIdx = i + 2; | ||
} | ||
numUnclosed += 2; | ||
i++; | ||
} else if (wikitext[i] === '}' && wikitext[i + 1] === '}') { | ||
if (numUnclosed === 2) { | ||
endIdx = i; | ||
let templateWikitext = wikitext.slice(startIdx, endIdx); // without braces | ||
let processed = processTemplateText(templateWikitext, config.namePredicate, config.templatePredicate); | ||
if (processed) { | ||
result.push(processed); | ||
} | ||
if (config.count && result.length === config.count) { | ||
return result; | ||
} | ||
} | ||
numUnclosed -= 2; | ||
i++; | ||
} else if (wikitext[i] === '|' && numUnclosed > 2) { | ||
// swap out pipes in nested templates with \x01 character | ||
wikitext = strReplaceAt(wikitext, i, '\x01'); | ||
} else if (/^<!--/.test(wikitext.slice(i, i + 4))) { | ||
inComment = true; | ||
i += 3; | ||
} else if (/^<nowiki ?>/.test(wikitext.slice(i, i + 9))) { | ||
inNowiki = true; | ||
i += 7; | ||
} | ||
} else { // we are in a comment or nowiki or {{{parameter}}} | ||
if (wikitext[i] === '|') { | ||
// swap out pipes with \x01 character | ||
wikitext = strReplaceAt(wikitext, i, '\x01'); | ||
} else if (/^-->/.test(wikitext.slice(i, i + 3))) { | ||
inComment = false; | ||
i += 2; | ||
} else if (/^<\/nowiki ?>/.test(wikitext.slice(i, i + 10))) { | ||
inNowiki = false; | ||
i += 8; | ||
} else if (wikitext[i] === '}' && wikitext[i + 1] === '}' && wikitext[i + 2] === '}') { | ||
inParameter = false; | ||
i += 2; | ||
} | ||
} | ||
} | ||
if (config.recursive) { | ||
let subtemplates = result.map(template => { | ||
return template.wikitext.slice(2, -2); | ||
}).filter(templateWikitext => { | ||
return /\{\{.*\}\}/s.test(templateWikitext); | ||
}).map(templateWikitext => { | ||
return Wikitext.parseTemplates(templateWikitext, config); | ||
}); | ||
return result.concat.apply(result, subtemplates); | ||
} | ||
return result; | ||
} | ||
/** | ||
* Remove a template, link, file or category from the text | ||
@@ -102,3 +203,3 @@ * CAUTION: If an entity with the very same wikitext exists earlier in the text, | ||
* | ||
* eg. let u = new bot.wikitext("Hello world <!-- world --> world"); | ||
* eg. let u = new bot.wikitext("Hello world <!-- world --> world"); | ||
* u.unbind('<!--','-->'); | ||
@@ -116,2 +217,10 @@ * u.content = u.content.replace(/world/g, 'earth'); | ||
unbind(prefix, postfix) { | ||
if (!this.unbinder) { | ||
this.unbinder = { | ||
counter: 0, | ||
history: {}, | ||
prefix: '%UNIQ::' + Math.random() + '::', | ||
postfix: '::UNIQ%' | ||
}; | ||
} | ||
let re = new RegExp(prefix + '([\\s\\S]*?)' + postfix, 'g'); | ||
@@ -125,3 +234,3 @@ this.text = this.text.replace(re, match => { | ||
} | ||
/** | ||
@@ -133,3 +242,2 @@ * Rebind after unbinding. | ||
let content = this.text; | ||
content.self = this; | ||
for (let [current, replacement] of Object.entries(this.unbinder.history)) { | ||
@@ -179,17 +287,17 @@ content = content.replace(current, replacement); | ||
// number of unclosed brackets | ||
let tlevel = 0, llevel = 0; | ||
let tlevel = 0, | ||
llevel = 0; | ||
let n = text.length; | ||
for (let i = 0; i < n; i++) { | ||
if (text[i] === '{' && text[i+1] === '{') { | ||
if (text[i] === '{' && text[i + 1] === '{') { | ||
tlevel++; | ||
i++; | ||
} else if (text[i] === '[' && text[i+1] === '[') { | ||
} else if (text[i] === '[' && text[i + 1] === '[') { | ||
llevel++; | ||
i++; | ||
} else if (text[i] === '}' && text[i+1] === '}') { | ||
} else if (text[i] === '}' && text[i + 1] === '}') { | ||
tlevel--; | ||
i++; | ||
} else if (text[i] === ']' && text[i+1] === ']') { | ||
} else if (text[i] === ']' && text[i + 1] === ']') { | ||
llevel--; | ||
@@ -229,3 +337,3 @@ i++; | ||
rows.forEach((row, idx) => { | ||
let cells = row.split(/^\|/m).slice(1); // slice(1) removes the emptiness or the row styles if present | ||
let cells = row.split(/^\|/m).slice(1); // slice(1) removes the emptiness or the row styles if present | ||
@@ -253,6 +361,7 @@ if (cells.length === 1) { // non-multilined | ||
/** | ||
* Parse sections from wikitext | ||
* @param {string} text | ||
* CAUTION: section header syntax in comments, nowiki tags, | ||
* pre, source or syntaxhighlight tags can lead to wrong results. | ||
* You're advised to run unbind() first. | ||
* @returns {{level: number, header: string, index: number, content: string}[]} array of | ||
@@ -263,11 +372,18 @@ * section objects. Each section object has the level, header, index (of beginning) and content. | ||
*/ | ||
parseSections() { | ||
return this.sections = Wikitext.parseSections(this.text); | ||
} | ||
// XXX: fix jsdocs | ||
/** | ||
* @inheritdoc | ||
*/ | ||
static parseSections(text) { | ||
const rgx = /^(=+)(.*?)\1/mg; | ||
let sections = [ | ||
{ | ||
level: 1, | ||
header: null, | ||
index: 0 | ||
} | ||
]; | ||
let sections = [{ | ||
level: 1, | ||
header: null, | ||
index: 0 | ||
}]; | ||
let match; | ||
@@ -298,6 +414,7 @@ while (match = rgx.exec(text)) { // eslint-disable-line no-cond-assign | ||
} | ||
var processLink = function (self, startIdx, endIdx) { | ||
var linktext = self.text.slice(startIdx, endIdx + 1); | ||
var [target, displaytext] = linktext.slice(2, -2).split('|'); | ||
var noSortkey = false; | ||
const processLink = function (self, startIdx, endIdx) { | ||
let linktext = self.text.slice(startIdx, endIdx + 1); | ||
let [target, displaytext] = linktext.slice(2, -2).split('|'); | ||
let noSortkey = false; | ||
if (!displaytext) { | ||
@@ -307,3 +424,3 @@ displaytext = target[0] === ':' ? target.slice(1) : target; | ||
} | ||
var title = bot.title.newFromText(target); | ||
let title = bot.title.newFromText(target); | ||
if (!title) { | ||
@@ -369,11 +486,12 @@ return; | ||
return this.parameters.find(p => { | ||
return p.name == paramName; | ||
return p.name == paramName; // == is intentional | ||
}); | ||
} | ||
getValue(paramName) { | ||
var param = this.getParam(paramName); | ||
let param = this.getParam(paramName); | ||
return param ? param.value : null; | ||
} | ||
setName(name) { | ||
this.name = name.trim(); | ||
name = name.trim(); | ||
this.name = name[0] ? (name[0].toUpperCase() + name.slice(1)) : name; | ||
this.nameTitle = bot.title.newFromText(name, 10); | ||
@@ -383,104 +501,3 @@ } | ||
// parseTemplates() and processTemplateText() are adapted from | ||
// https://en.wikipedia.org/wiki/MediaWiki:Gadget-libExtraUtil.js written by Evad37 | ||
// which was in turn adapted from https://en.wikipedia.org/wiki/User:SD0001/parseAllTemplates.js | ||
// written by me. (cc-by-sa/GFDL) | ||
/** | ||
* @inheritdoc | ||
*/ | ||
const parseTemplates = function (wikitext, config) { | ||
config = config || { | ||
recursive: false, | ||
namePredicate: null, | ||
templatePredicate: null, | ||
count: null | ||
}; | ||
const result = []; | ||
const n = wikitext.length; | ||
// number of unclosed braces | ||
var numUnclosed = 0; | ||
// are we inside a comment, or between nowiki tags, or in a {{{parameter}}}? | ||
var inComment = false; | ||
var inNowiki = false; | ||
var inParameter = false; | ||
var startIdx, endIdx; | ||
for (var i = 0; i < n; i++) { | ||
if (!inComment && !inNowiki && !inParameter) { | ||
if (wikitext[i] === '{' && wikitext[i + 1] === '{' && wikitext[i + 2] === '{' && wikitext[i + 3] !== '{') { | ||
inParameter = true; | ||
i += 2; | ||
} else if (wikitext[i] === '{' && wikitext[i + 1] === '{') { | ||
if (numUnclosed === 0) { | ||
startIdx = i + 2; | ||
} | ||
numUnclosed += 2; | ||
i++; | ||
} else if (wikitext[i] === '}' && wikitext[i + 1] === '}') { | ||
if (numUnclosed === 2) { | ||
endIdx = i; | ||
var templateWikitext = wikitext.slice(startIdx, endIdx); // without braces | ||
var processed = processTemplateText(templateWikitext, config.namePredicate, config.templatePredicate); | ||
if (processed) { | ||
result.push(processed); | ||
} | ||
if (config.count && result.length === config.count) { | ||
return result; | ||
} | ||
} | ||
numUnclosed -= 2; | ||
i++; | ||
} else if (wikitext[i] === '|' && numUnclosed > 2) { | ||
// swap out pipes in nested templates with \1 character | ||
wikitext = strReplaceAt(wikitext, i, '\1'); | ||
} else if (/^<!--/.test(wikitext.slice(i, i + 4))) { | ||
inComment = true; | ||
i += 3; | ||
} else if (/^<nowiki ?>/.test(wikitext.slice(i, i + 9))) { | ||
inNowiki = true; | ||
i += 7; | ||
} | ||
} else { // we are in a comment or nowiki or {{{parameter}}} | ||
if (wikitext[i] === '|') { | ||
// swap out pipes with \1 character | ||
wikitext = strReplaceAt(wikitext, i, '\1'); | ||
} else if (/^-->/.test(wikitext.slice(i, i + 3))) { | ||
inComment = false; | ||
i += 2; | ||
} else if (/^<\/nowiki ?>/.test(wikitext.slice(i, i + 10))) { | ||
inNowiki = false; | ||
i += 8; | ||
} else if (wikitext[i] === '}' && wikitext[i + 1] === '}' && wikitext[i + 2] === '}') { | ||
inParameter = false; | ||
i += 2; | ||
} | ||
} | ||
} | ||
if (config.recursive) { | ||
var subtemplates = result.map(template => { | ||
return template.wikitext.slice(2, -2); | ||
}).filter(templateWikitext => { | ||
return /\{\{.*\}\}/s.test(templateWikitext); | ||
}).map(templateWikitext => { | ||
return parseTemplates(templateWikitext, config); | ||
}); | ||
return result.concat.apply(result, subtemplates); | ||
} | ||
return result; | ||
}; | ||
/** | ||
* @param {string} text - template wikitext without braces, with the pipes in | ||
@@ -492,17 +509,19 @@ * nested templates replaced by \x01 | ||
const template = new Template('{{' + text.replace(/\1/g, '|') + '}}'); | ||
// eslint-disable-next-line no-control-regex | ||
const template = new Template('{{' + text.replace(/\x01/g, '|') + '}}'); | ||
// swap out pipe in links with \1 control character | ||
// swap out pipe in links with \x01 control character | ||
// [[File: ]] can have multiple pipes, so might need multiple passes | ||
while (/(\[\[[^\]]*?)\|(.*?\]\])/g.test(text)) { | ||
text = text.replace(/(\[\[[^\]]*?)\|(.*?\]\])/g, '$1\1$2'); | ||
text = text.replace(/(\[\[[^\]]*?)\|(.*?\]\])/g, '$1\x01$2'); | ||
} | ||
const [name, ...parameterChunks] = text.split('|').map(chunk => { | ||
// change '\1' control characters back to pipes | ||
return chunk.replace(/\1/g, '|'); | ||
// change '\x01' control characters back to pipes | ||
// eslint-disable-next-line no-control-regex | ||
return chunk.replace(/\x01/g, '|'); | ||
}); | ||
template.setName(name); | ||
if (namePredicate && !namePredicate(template.name)) { | ||
@@ -512,12 +531,12 @@ return null; | ||
var unnamedIdx = 1; | ||
let unnamedIdx = 1; | ||
parameterChunks.forEach(function (chunk) { | ||
var indexOfEqualTo = chunk.indexOf('='); | ||
var indexOfOpenBraces = chunk.indexOf('{{'); | ||
let indexOfEqualTo = chunk.indexOf('='); | ||
let indexOfOpenBraces = chunk.indexOf('{{'); | ||
var isWithoutEquals = !chunk.includes('='); | ||
var hasBracesBeforeEquals = chunk.includes('{{') && indexOfOpenBraces < indexOfEqualTo; | ||
var isUnnamedParam = (isWithoutEquals || hasBracesBeforeEquals); | ||
let isWithoutEquals = !chunk.includes('='); | ||
let hasBracesBeforeEquals = chunk.includes('{{') && indexOfOpenBraces < indexOfEqualTo; | ||
let isUnnamedParam = (isWithoutEquals || hasBracesBeforeEquals); | ||
var pName, pNum, pVal; | ||
let pName, pNum, pVal; | ||
if (isUnnamedParam) { | ||
@@ -551,2 +570,2 @@ // Get the next number not already used by either an unnamed parameter, | ||
}; | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
173741
13
4578
7
+ Addedeventsource@^1.0.7
+ Addedeventsource@1.1.2(transitive)