mirror of
				https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
				synced 2025-11-04 12:05:21 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1956 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1956 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						||
 * @name Wikiapi.js
 | 
						||
 * 
 | 
						||
 * @fileoverview Main codes of module wikiapi (class Wikiapi)
 | 
						||
 */
 | 
						||
 | 
						||
'use strict';
 | 
						||
 | 
						||
/**
 | 
						||
 * @description CeJS controller
 | 
						||
 * 
 | 
						||
 * @type Function
 | 
						||
 * @inner
 | 
						||
 * 
 | 
						||
 * @see https://github.com/kanasimi/CeJS
 | 
						||
 */
 | 
						||
let CeL;
 | 
						||
 | 
						||
try {
 | 
						||
	// Load CeJS library.
 | 
						||
	CeL = require('cejs');
 | 
						||
	if (typeof CeL.then === 'function' && typeof window === "object" && window.CeL) {
 | 
						||
		// assert: @Snowpack
 | 
						||
		CeL = window.CeL;
 | 
						||
	}
 | 
						||
 | 
						||
} catch (e) /* istanbul ignore next: Only for debugging locally */ {
 | 
						||
	// https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md
 | 
						||
	// const Wikiapi = require('./wikiapi.js');
 | 
						||
	require('./_CeL.loader.nodejs.js');
 | 
						||
	CeL = globalThis.CeL;
 | 
						||
}
 | 
						||
// assert: typeof CeL === 'function'
 | 
						||
 | 
						||
// Load modules.
 | 
						||
// @see `wiki loader.js`:
 | 
						||
// https://github.com/kanasimi/wikibot/blob/master/wiki%20loader.js
 | 
						||
CeL.run(['interact.DOM', 'application.debug',
 | 
						||
	// 載入不同地區語言的功能 for wiki.work()。
 | 
						||
	'application.locale',
 | 
						||
	// 載入操作維基百科的主要功能。
 | 
						||
	'application.net.wiki',
 | 
						||
	// Optional 可選功能
 | 
						||
	'application.net.wiki.data', 'application.net.wiki.admin',
 | 
						||
	// Add color to console messages. 添加主控端報告的顏色。
 | 
						||
	'interact.console',
 | 
						||
	// for 'application.platform.nodejs': CeL.env.arg_hash, wiki_API.cache(),
 | 
						||
	// CeL.fs_mkdir(), wiki_API.read_dump()
 | 
						||
	'application.storage']);
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @description syntactic sugar for CeJS MediaWiki module. CeL.net.wiki === CeL.wiki
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
const wiki_API = CeL.net.wiki;
 | 
						||
/**
 | 
						||
 * key to get {@link wiki_API} operator when using {@link wiki_API}.
 | 
						||
 * 
 | 
						||
 * @type Symbol
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
const KEY_SESSION = wiki_API.KEY_SESSION;
 | 
						||
 | 
						||
// Set default language. 改變預設之語言。
 | 
						||
wiki_API.set_language('en');
 | 
						||
 | 
						||
/**
 | 
						||
 * @description key to get {@link wiki_API} operator inside {@link Wikiapi}.
 | 
						||
 * <code>this[KEY_wiki_session]</code> inside module code will get {@link wiki_API} operator.
 | 
						||
 * 
 | 
						||
 * @type Symbol
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
const KEY_wiki_session = Symbol('wiki_API session');
 | 
						||
// for debug
 | 
						||
// Wikiapi.KEY_wiki_session = KEY_wiki_session;
 | 
						||
 | 
						||
/**
 | 
						||
 * @description main Wikiapi operator 操作子.
 | 
						||
 * 
 | 
						||
 * @param {String|Object} [API_URL]	- language code or service endpoint of MediaWiki project.<br />
 | 
						||
 *            Input {Object} will be treat as options.
 | 
						||
 * 
 | 
						||
 * @class
 | 
						||
 */
 | 
						||
function Wikiapi(API_URL) {
 | 
						||
	const wiki_session = new wiki_API(null, null, API_URL);
 | 
						||
	// this[KEY_wiki_session] = new wiki_API(null, null, API_URL);
 | 
						||
	setup_wiki_session.call(this, wiki_session);
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @description Bind {@link wiki_API} instance to {@link Wikiapi} instance
 | 
						||
 * 
 | 
						||
 * @param {wiki_API} wiki_session	- wiki_API session
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
function setup_wiki_session(wiki_session) {
 | 
						||
	Object.defineProperty(this, KEY_wiki_session, {
 | 
						||
		value: wiki_session,
 | 
						||
		writable: true,
 | 
						||
	});
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias login
 | 
						||
 * @description login into the target MediaWiki API using the provided username and password.
 | 
						||
 * For bots, see [[Special:BotPasswords]] on your wiki.
 | 
						||
 * 
 | 
						||
 * @param {String} user_name	- Account username.
 | 
						||
 * @param {String} password		- Account's password.
 | 
						||
 * @param {String} [API_URL]	- API URL of target wiki site.
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {String} login_name
 | 
						||
 *
 | 
						||
 * @example <caption><span id="example__Login to wiki site 1">Login to wiki site method 1.</span></caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
const login_options = {
 | 
						||
	user_name: '', password: '', API_URL: 'en',
 | 
						||
	// Ror lingualibre only. @see https://github.com/kanasimi/wikibot/blob/master/wiki%20configuration.sample.js
 | 
						||
	//data_API_URL: 'https://lingualibre.org/api.php',
 | 
						||
	//SPARQL_API_URL: 'https://lingualibre.org/bigdata/namespace/wdq/sparql',
 | 
						||
	// Calling in another domain
 | 
						||
	origin: '*'
 | 
						||
};
 | 
						||
await wiki.login(login_options);
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption><span id="example__Login to wiki site 2">Login to wiki site method 2.</span></caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
await wiki.login('user_name', 'password', 'en');
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_login(user_name, password, API_URL) {
 | 
						||
	let options;
 | 
						||
	if (!password && !API_URL && CeL.is_Object(user_name)) {
 | 
						||
		options = user_name;
 | 
						||
	} else if (CeL.is_Object(API_URL)) {
 | 
						||
		options = { ...API_URL, user_name, password };
 | 
						||
	} else {
 | 
						||
		options = { user_name, password, API_URL };
 | 
						||
	}
 | 
						||
 | 
						||
	function Wikiapi_login_executor(resolve, reject) {
 | 
						||
		const wiki_session = wiki_API.login({
 | 
						||
			preserve_password: true,
 | 
						||
			...options,
 | 
						||
 | 
						||
			API_URL: options.API_URL || this[KEY_wiki_session].API_URL,
 | 
						||
			callback(login_name, error) {
 | 
						||
				if (error) {
 | 
						||
					reject(error);
 | 
						||
				} else {
 | 
						||
					resolve(login_name);
 | 
						||
				}
 | 
						||
			},
 | 
						||
			// task_configuration_page: 'page title',
 | 
						||
		});
 | 
						||
		setup_wiki_session.call(this, wiki_session);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_login_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @description attributes of {Object} page_data, will setup by {@link set_page_data_attributes}.
 | 
						||
 * 
 | 
						||
 * @type Object
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
const page_data_attributes = {
 | 
						||
	/**
 | 
						||
	 * @description get {String}page content, maybe undefined.
 | 
						||
	 * 條目/頁面內容 = wiki_API.revision_content(revision)
 | 
						||
	 *
 | 
						||
	 * @type String
 | 
						||
	 */
 | 
						||
	wikitext: {
 | 
						||
		get() {
 | 
						||
			// console.trace(this);
 | 
						||
			// console.log(wiki_API.content_of(this, 0));
 | 
						||
			return wiki_API.content_of(this, 0);
 | 
						||
		}
 | 
						||
	},
 | 
						||
	/**
 | 
						||
	 * @description get {Object}revisions
 | 
						||
	 *
 | 
						||
	 * @type Object
 | 
						||
	 */
 | 
						||
	revision: {
 | 
						||
		value: function revision(revision_NO) {
 | 
						||
			return wiki_API.content_of(this, revision_NO);
 | 
						||
		}
 | 
						||
	},
 | 
						||
	/**
 | 
						||
	 * @description get {Attay} parsed data of page_data
 | 
						||
	 *
 | 
						||
	 * @type Array
 | 
						||
	 */
 | 
						||
	parse: {
 | 
						||
		value: function parse(options) {
 | 
						||
			// this === page_data
 | 
						||
 | 
						||
			// options = { ...options, [KEY_SESSION]: this[KEY_wiki_session] };
 | 
						||
			options = Wikiapi.prototype.append_session_to_options.call(this, options);
 | 
						||
 | 
						||
			// using function parse_page(options) @ wiki_API
 | 
						||
			return wiki_API.parser(this, options).parse();
 | 
						||
			// return {Array}parsed
 | 
						||
		}
 | 
						||
	},
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * @description Bind {@link page_data_attributes} to <code>page_data</code>
 | 
						||
 * 
 | 
						||
 * @param {Object} page_data	- page data
 | 
						||
 * @param {wiki_API} wiki		- wiki_API session
 | 
						||
 * 
 | 
						||
 * @returns {Promise} Promise object represents {Object} page's data
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
function set_page_data_attributes(page_data, wiki) {
 | 
						||
	// `page_data` maybe non-object when error occurres.
 | 
						||
	if (page_data) {
 | 
						||
		page_data[KEY_wiki_session] = wiki;
 | 
						||
		Object.defineProperties(page_data, page_data_attributes);
 | 
						||
	}
 | 
						||
	return page_data;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias page
 | 
						||
 * @description given a title, returns the page's data.
 | 
						||
 * 
 | 
						||
 * @param {String} title		- page title
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Object} page's data
 | 
						||
 *
 | 
						||
 * @example <caption>load page</caption>
 | 
						||
// <code>
 | 
						||
// on Wikipedia...
 | 
						||
const wiki = new Wikiapi('en');
 | 
						||
// ...or other MediaWiki websites
 | 
						||
//const wiki = new Wikiapi('https://awoiaf.westeros.org/api.php');
 | 
						||
let page_data = await wiki.page('Universe', {
 | 
						||
	// You may also set rvprop.
 | 
						||
	//rvprop: 'ids|content|timestamp|user',
 | 
						||
});
 | 
						||
console.log(page_data.wikitext);
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Get multi revisions</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
let page_data = await wiki.page('Universe', {
 | 
						||
	// Get multi revisions
 | 
						||
	revisions: 2
 | 
						||
});
 | 
						||
console.log(page_data.wikitext);
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>parse wiki page (The parser is more powerful than the example. Please refer to link of wikitext parser examples showing in "Features" section of README.md.)</caption>
 | 
						||
// <code>
 | 
						||
// Usage with other language
 | 
						||
const zhwiki = new Wikiapi('zh');
 | 
						||
await zhwiki.login('user', 'password');
 | 
						||
let page_data = await zhwiki.page('Universe');
 | 
						||
 | 
						||
// `page_data.parse(options)` will startup the parser process, create page_data.parsed. After .parse(), we can use parsed.each().
 | 
						||
const parsed = page_data.parse();
 | 
						||
 | 
						||
// See all type in wiki_toString @ https://github.com/kanasimi/CeJS/tree/master/application/net/wiki/parser/wikitext.js
 | 
						||
// List all template name.
 | 
						||
parsed.each('template', token => console.log(token.name));
 | 
						||
// List all [[Template:Tl]] token.
 | 
						||
parsed.each('Template:Tl', token => console.log(token));
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Get information from Infobox template</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi('en');
 | 
						||
const page_data = await wiki.page('JavaScript');
 | 
						||
const parsed = page_data.parse();
 | 
						||
let infobox;
 | 
						||
// Read Infobox templates, convert to JSON.
 | 
						||
parsed.each('template', template_token => {
 | 
						||
	if (template_token.name.startsWith('Infobox')) {
 | 
						||
		infobox = template_token.parameters;
 | 
						||
		return parsed.each.exit;
 | 
						||
	}
 | 
						||
});
 | 
						||
for (const [key, value] of Object.entries(infobox))
 | 
						||
	infobox[key] = value.toString();
 | 
						||
// print json of the infobox
 | 
						||
console.log(infobox);
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_page(title, options) {
 | 
						||
	function Wikiapi_page_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.page(title, (page_data, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(set_page_data_attributes(page_data, wiki));
 | 
						||
			}
 | 
						||
		}, {
 | 
						||
			// node.js v12.22.7: Cannot use "?."
 | 
						||
			rvlimit: options && options.revisions,
 | 
						||
			...options
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_page_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias tracking_revisions
 | 
						||
 * @description tracking revisions to lookup what revision had added / removed <code>to_search</code>.
 | 
						||
 * 
 | 
						||
 * @param {String} title		- page title
 | 
						||
 * @param {String} to_search	- filter / text to search. to_search(diff, revision, old_revision): `diff` 為從舊的版本 `old_revision` 改成 `revision` 時的差異。
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 * 
 | 
						||
 * @returns {Promise} Promise object represents {Object} newer_revision,
 | 
						||
 *          newer_revision.page: page_data
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_tracking_revisions(title, to_search, options) {
 | 
						||
	function Wikiapi_tracking_revisions_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.tracking_revisions(title, to_search, (revision, page_data, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				if (!revision)
 | 
						||
					revision = Object.create(null);
 | 
						||
				revision.page = page_data;
 | 
						||
				resolve(revision);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_tracking_revisions_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @description Handle the result of MediaWiki API when executing edit operation.
 | 
						||
 * 
 | 
						||
 * @param {Function} reject	- reject function
 | 
						||
 * @param {any} error		- error object / message
 | 
						||
 * @param {any} [result]	- result of MediaWiki API
 | 
						||
 *
 | 
						||
 * @returns {Boolean} Return <code>true</code> if the edit operation failed.
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
function reject_edit_error(reject, error, result) {
 | 
						||
	// skip_edit is not error
 | 
						||
	if (!error
 | 
						||
		// @see wiki_API_edit.check_data
 | 
						||
		|| Array.isArray(error) && error[0] === Wikiapi.skip_edit[0]) {
 | 
						||
		return;
 | 
						||
	}
 | 
						||
 | 
						||
	if (Array.isArray(error) && typeof error[1] === 'string') {
 | 
						||
		// console.log('' + reject);
 | 
						||
		// console.trace(error);
 | 
						||
		error = error[1];
 | 
						||
		const error_object = new Error(error);
 | 
						||
		error_object.from_string = error;
 | 
						||
		error = error_object
 | 
						||
		// console.log(error);
 | 
						||
	}
 | 
						||
 | 
						||
	if (result && typeof error === 'object')
 | 
						||
		error.result = result;
 | 
						||
	reject(error);
 | 
						||
	return true;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias edit_page
 | 
						||
 * @description edits content of target page.<br />
 | 
						||
 * Note: for multiple pages, you should use {@link Wikiapi#for_each_page}.<br />
 | 
						||
 * Note: The function will check sections of [[User talk:user name/Stop]] if somebody tells us needed to stop edit. See <a href="https://zh.wikipedia.org/wiki/User:Cewbot/Stop">mechanism to stop operations</a>.
 | 
						||
 * 
 | 
						||
 * @param {String} title			- page title
 | 
						||
 * @param {String|Function} content	- 'wikitext page content' || page_data => 'wikitext'
 | 
						||
 * @param {Object} [options]		- options to run this function. e.g., { summary: '', bot: 1, nocreate: 1, minor: 1 }
 | 
						||
 * 
 | 
						||
 * @returns {Promise} Promise object represents {Object} result of MediaWiki API
 | 
						||
 *
 | 
						||
 * @example <caption>edit page: method 1: basic operation</caption>
 | 
						||
// <code>
 | 
						||
const enwiki = new Wikiapi;
 | 
						||
await enwiki.login('bot name', 'password', 'en');
 | 
						||
 | 
						||
const SB_page_data = await enwiki.page('Wikipedia:Sandbox');
 | 
						||
// You may do some operations on SB_page_data
 | 
						||
const parsed = SB_page_data.parse();
 | 
						||
parsed.each('template', template_token => {
 | 
						||
	// modify template token
 | 
						||
});
 | 
						||
// and then edit it. ** You MUST call enwiki.page() before enwiki.edit()! **
 | 
						||
await enwiki.edit(parsed.toString(), { bot: 1, minor: 1, nocreate: 1 });
 | 
						||
 | 
						||
// exmaple 2: append text in the tail of page content
 | 
						||
await enwiki.edit(page_data => {
 | 
						||
	return page_data.wikitext
 | 
						||
		+ '\nTest edit using {{GitHub|kanasimi/wikiapi}}.';
 | 
						||
}, { bot: 1 });
 | 
						||
 | 
						||
// exmaple 3: replace page content
 | 
						||
await enwiki.edit('Just replace by this wikitext', { bot: 1, minor: 1, nocreate: 1, summary: 'test edit' });
 | 
						||
 | 
						||
// exmaple 4: append a new section
 | 
						||
await enwiki.edit('section content', {
 | 
						||
	section: 'new',
 | 
						||
	sectiontitle: 'section title',
 | 
						||
	nocreate : 1,
 | 
						||
	summary: 'test edit',
 | 
						||
});
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>edit page: method 2: modufy summary inside function</caption>
 | 
						||
// <code>
 | 
						||
const enwiki = new Wikiapi;
 | 
						||
await enwiki.login('bot name', 'password', 'en');
 | 
						||
await enwiki.edit_page('Wikipedia:Sandbox', function (page_data) {
 | 
						||
	this.summary += ': You may set additional summary inside the function';
 | 
						||
	delete this.minor;
 | 
						||
	return page_data.wikitext
 | 
						||
		+ '\nTest edit using {{GitHub|kanasimi/wikiapi}}.';
 | 
						||
}, { bot: 1, nocreate: 1, minor: 1, redirects: 1, summary: 'test edit' });
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_edit_page(title, content, options) {
 | 
						||
	function Wikiapi_edit_page_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
 | 
						||
		// console.trace([title, content]);
 | 
						||
		// console.trace(`Wikiapi_edit_page 1: ${wiki_API.title_link_of(title)}, ${wiki.actions.length} actions, ${wiki.running}/${wiki.thread_count}/${wiki.actions[wiki_API.KEY_waiting_callback_result_relying_on_this]}.`);
 | 
						||
		// console.trace(title);
 | 
						||
		// CeL.set_debug(6);
 | 
						||
		if (title) {
 | 
						||
			// console.trace(wiki);
 | 
						||
			options = { ...options, error_with_symbol: true };
 | 
						||
			// 預防 page 本身是非法的頁面標題。當 session.page() 出錯時,將導致沒有 .last_page。
 | 
						||
			if (wiki_API.is_page_data(title))
 | 
						||
				options.task_page_data = title;
 | 
						||
			// call wiki_API_prototype_method() @ CeL.application.net.wiki.list
 | 
						||
			wiki.page(title, (page_data, error) => {
 | 
						||
				// console.trace('Set .page_to_edit:');
 | 
						||
				// console.log([title, page_data, error]);
 | 
						||
				// console.log(wiki.actions[0]);
 | 
						||
 | 
						||
				// 手動指定要編輯的頁面。避免多執行續打亂 wiki.last_page。
 | 
						||
				options.page_to_edit = page_data;
 | 
						||
			}, options);
 | 
						||
		}
 | 
						||
		// console.trace(`Wikiapi_edit_page 2: ${wiki_API.title_link_of(title)}, ${wiki.actions.length} actions, ${wiki.running}/${wiki.thread_count}/${wiki.actions[wiki_API.KEY_waiting_callback_result_relying_on_this]}.`);
 | 
						||
		// console.trace(wiki);
 | 
						||
		// console.trace(wiki.last_page);
 | 
						||
 | 
						||
		// wiki.edit(page contents, options, callback)
 | 
						||
		wiki.edit(typeof content === 'function' ? function (page_data) {
 | 
						||
			return content.call(this, set_page_data_attributes(page_data, wiki));
 | 
						||
		} : content, options, (title, error, result) => {
 | 
						||
			// console.trace('Wikiapi_edit_page: callbacked');
 | 
						||
			// console.log(title);
 | 
						||
			// console.log(wiki.running);
 | 
						||
			// CeL.set_debug(6);
 | 
						||
 | 
						||
			if (!reject_edit_error(reject, error, result)) {
 | 
						||
				// console.log('Wikiapi_edit_page: resolve');
 | 
						||
				resolve(title);
 | 
						||
			}
 | 
						||
			// console.log('Wikiapi_edit_page: callback() return');
 | 
						||
		});
 | 
						||
 | 
						||
		// console.trace(`Wikiapi_edit_page 3: ${wiki_API.title_link_of(title)}, ${wiki.actions.length} actions, ${wiki.running}/${wiki.thread_count}/${wiki.actions[wiki_API.KEY_waiting_callback_result_relying_on_this]}.`);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_edit_page_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// <code>return Wikiapi.skip_edit;</code> as a symbol to skip this edit, do not generate
 | 
						||
// warning message.
 | 
						||
// 可以利用 ((return [ wiki_API.edit.cancel, 'reason' ];)) 來回傳 reason。
 | 
						||
// ((return [ wiki_API.edit.cancel, 'skip' ];)) 來跳過 (skip) 本次編輯動作,不特別顯示或處理。
 | 
						||
// 被 skip/pass 的話,連警告都不顯現,當作正常狀況。
 | 
						||
/**
 | 
						||
 * @description Return <code>Wikiapi.skip_edit</code> when we running edit function, but do not want to edit current page.
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi
 | 
						||
 */
 | 
						||
Wikiapi.skip_edit = [wiki_API.edit.cancel, 'skip'];
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias move_page
 | 
						||
 * @description Move page <code>move_from_title</code> to <code>move_to_title</code>.
 | 
						||
 *
 | 
						||
 * @param {Object|String} move_from_title	- move from title
 | 
						||
 * @param {Object|String} move_to_title		- move to title
 | 
						||
 * @param {Object} [options]				- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {String} result of MediaWiki API
 | 
						||
 *
 | 
						||
 * @example <caption>Move <code>move_from_title</code> to <code>move_to_title</code>.</caption>
 | 
						||
// <code>
 | 
						||
await wiki.move_page(move_from_title, move_to_title, { reason, noredirect: true, movetalk: true });
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_move_page(move_from_title, move_to_title, options) {
 | 
						||
	function Wikiapi_move_page_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		// using wiki_API.prototype.move_page()
 | 
						||
		wiki.move_page(move_from_title, move_to_title, options, (data, error) => {
 | 
						||
			if (error) {
 | 
						||
				/**
 | 
						||
				 * <code>
 | 
						||
 | 
						||
				e.g., { code: 'articleexists', info: 'A page of that name already exists, or the name you have chosen is not valid. Please choose another name.', '*': '...' }
 | 
						||
				e.g., { code: 'missingtitle', info: "The page you specified doesn't exist.", '*': '...' }
 | 
						||
 | 
						||
				</code>
 | 
						||
				 */
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				/**
 | 
						||
				 * <code>
 | 
						||
 | 
						||
				e.g., { from: 'from', to: 'to', reason: 'move', redirectcreated: '', moveoverredirect: '' }
 | 
						||
 | 
						||
				</code>
 | 
						||
				 */
 | 
						||
				resolve(data);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_move_page_executor.bind(this));
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias move_to
 | 
						||
 * @description Move to <code>move_to_title</code>. <em>Must call {@link Wikiapi#page} first!</em>
 | 
						||
 * 
 | 
						||
 * @param {Object|String} move_to_title	- move to title
 | 
						||
 * @param {Object} [options]			- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {String} result of MediaWiki API
 | 
						||
 *
 | 
						||
 * @example <caption>Move <code>move_from_title</code> to <code>move_to_title</code>.</caption>
 | 
						||
// <code>
 | 
						||
page_data = await wiki.page(move_from_title);
 | 
						||
await wiki.move_to(move_to_title, { reason: reason, noredirect: true, movetalk: true });
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_move_to(move_to_title, options) {
 | 
						||
	function Wikiapi_move_to_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		if (!wiki.last_page) {
 | 
						||
			reject(new Error(Wikiapi_move_to.name + ': Must call .page() first! '
 | 
						||
				// gettext_config:{"id":"cannot-move-to-$1"}
 | 
						||
				+ CeL.gettext('Cannot move to %1', wiki_API.title_link_of(move_to_title))));
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		// using wiki_API.prototype.move_to()
 | 
						||
		wiki.move_to(move_to_title, options, (data, error) => {
 | 
						||
			if (error) {
 | 
						||
				/**
 | 
						||
				 * <code>
 | 
						||
 | 
						||
				e.g., { code: 'articleexists', info: 'A page of that name already exists, or the name you have chosen is not valid. Please choose another name.', '*': '...' }
 | 
						||
				e.g., { code: 'missingtitle', info: "The page you specified doesn't exist.", '*': '...' }
 | 
						||
 | 
						||
				</code>
 | 
						||
				 */
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				/**
 | 
						||
				 * <code>
 | 
						||
 | 
						||
				e.g., { from: 'from', to: 'to', reason: 'move', redirectcreated: '', moveoverredirect: '' }
 | 
						||
 | 
						||
				</code>
 | 
						||
				 */
 | 
						||
				resolve(data);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_move_to_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias query
 | 
						||
 * @description query MediaWiki API manually
 | 
						||
 * 
 | 
						||
 * @param {Object} parameters	- parameters to call MediaWiki API
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Object} result of MediaWiki API
 | 
						||
 *
 | 
						||
 * @example <caption>query flow-parsoid-utils</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi('mediawiki');
 | 
						||
const results = await wiki.query({
 | 
						||
	action: "flow-parsoid-utils",
 | 
						||
	content: "<b>bold</b> & <i>italic</i>",
 | 
						||
	title: "MediaWiki", from: "html", to: "wikitext"
 | 
						||
});
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_query(parameters, options) {
 | 
						||
	function Wikiapi_query_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.query_API(parameters, (data, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(data);
 | 
						||
			}
 | 
						||
		}, {
 | 
						||
			post_data_only: true,
 | 
						||
			...options
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_query_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias purge
 | 
						||
 * @description Purge the cache for the given title.
 | 
						||
 * 
 | 
						||
 * @param {Object} title		- page title
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Object} page_data
 | 
						||
 *
 | 
						||
 * @example <caption>query flow-parsoid-utils</caption>
 | 
						||
// <code>
 | 
						||
const metawiki = new Wikiapi('meta');
 | 
						||
let page_data = await metawiki.purge('Project:Sandbox');
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_purge(title, options) {
 | 
						||
	if (CeL.is_Object(title) && !options) {
 | 
						||
		// shift arguments.
 | 
						||
		[title, options] = [null, title];
 | 
						||
	}
 | 
						||
 | 
						||
	function Wikiapi_purge_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		if (title) {
 | 
						||
			wiki.page(title);
 | 
						||
		}
 | 
						||
		// using wiki_API.purge
 | 
						||
		wiki.purge((data, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(data);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_purge_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @description Bind properties to {@link wiki_API} data entity.
 | 
						||
 * 設定 wikidata entity object,讓我們能直接操作 entity.modify(),並且避免洩露 wiki_API session。
 | 
						||
 * 
 | 
						||
 * @param {Object} data_entity	- wiki_API data entity
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
function setup_data_entity(data_entity) {
 | 
						||
	if (!data_entity)
 | 
						||
		return;
 | 
						||
	// assert: data_entity[KEY_SESSION].host === this
 | 
						||
	// console.trace(data_entity[KEY_SESSION].host === this);
 | 
						||
	delete data_entity[KEY_SESSION];
 | 
						||
 | 
						||
	Object.defineProperties(data_entity, {
 | 
						||
		[KEY_wiki_session]: { value: this },
 | 
						||
		modify: { value: modify_data_entity },
 | 
						||
	});
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * @description Modify data entity
 | 
						||
 * 
 | 
						||
 * @param {Object} data_entity	- wiki_API data entity
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 * 
 | 
						||
 * @returns {Promise} Promise object represents {Object} result data entity
 | 
						||
 * 
 | 
						||
 * @inner
 | 
						||
 */
 | 
						||
function modify_data_entity(data_to_modify, options) {
 | 
						||
	function modify_data_entity_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		// console.trace(wiki);
 | 
						||
 | 
						||
		// using function wikidata_edit() @
 | 
						||
		// https://github.com/kanasimi/CeJS/blob/master/application/net/wiki/data.js
 | 
						||
		// wiki.edit_data(id, data, options, callback)
 | 
						||
		wiki.data(this).edit_data(data_to_modify || this, options, (data_entity, error) => {
 | 
						||
			// console.trace([data_entity, error]);
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				setup_data_entity.call(wiki, data_entity);
 | 
						||
				resolve(data_entity);
 | 
						||
			}
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(modify_data_entity_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias data
 | 
						||
 * @description Get wikidata entity / property
 | 
						||
 *
 | 
						||
 * @param {Object} data_entity	- wiki_API data entity
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Object} wikidata entity / property
 | 
						||
 *
 | 
						||
 * @example <caption>Get wikidata entity method 1</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
const data_entity = await wiki.data('Q1');
 | 
						||
// Work with other language
 | 
						||
console.assert(CeL.wiki.data.value_of(data_entity.labels.zh) === '宇宙');
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Get wikidata entity of [[Human]]</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
const page_data = await wiki.page('Human');
 | 
						||
const data_entity = await wiki.data(page_data);
 | 
						||
console.assert(CeL.wiki.data.value_of(data_entity.labels.zh) === '人類');
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Get wikidata entity method 2: Get P1419 of wikidata entity: 'Universe'</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
// Read, access by title (English), access property P1419
 | 
						||
let data = await wiki.data('Universe', 'P1419');
 | 
						||
// assert: {Array}data = [ 'shape of the universe', '', ... ]
 | 
						||
console.assert(data.includes('shape of the universe'));
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>update wikidata</caption>
 | 
						||
// <code>
 | 
						||
// Just for test
 | 
						||
delete CeL.wiki.query.default_maxlag;
 | 
						||
const wiki = new Wikiapi;
 | 
						||
await wiki.login('user', 'password', 'test');
 | 
						||
 | 
						||
// Get https://test.wikidata.org/wiki/Q7
 | 
						||
let entity = await wiki.data('Q7');
 | 
						||
// search [ language, label ]
 | 
						||
//entity = await wiki.data(['en', 'Earth']);
 | 
						||
 | 
						||
// Reset claim
 | 
						||
entity = await wiki.data('Q1841');
 | 
						||
await entity.modify({ claims: [{ P3: "old.wav", remove: true }] }, { bot: 1, summary: 'test edit: Remove specific value' });
 | 
						||
// Warning: If you want to perform multiple operations on the same property, you need to get the entity again!
 | 
						||
entity = await wiki.data('Q1841');
 | 
						||
await entity.modify({ claims: [{ P3: "new.wav" }] }, { bot: 1, summary: 'test edit: Add value' });
 | 
						||
 | 
						||
// Update claim
 | 
						||
await entity.modify({ claims: [{ P17: 'Q213280' }] }, { bot: 1, summary: 'test edit: Update claim' });
 | 
						||
 | 
						||
// Update claim: set country (P17) to 'Test Country 1' (Q213280) ([language, label] as entity)
 | 
						||
await entity.modify({ claims: [{ language: 'en', country: [, 'Test Country 1'] }] }, { summary: '' });
 | 
						||
 | 
						||
// Remove country (P17) : 'Test Country 1' (Q213280)
 | 
						||
await entity.modify({ claims: [{ language: 'en', country: [, 'Test Country 1'], remove: true }] }, { summary: '' });
 | 
						||
 | 
						||
// Update label
 | 
						||
await entity.modify({ labels: [{ language: 'zh-tw', value: '地球' }] }, { summary: '' });
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_data(key, property, options) {
 | 
						||
	if (CeL.is_Object(property) && !options) {
 | 
						||
		// shift arguments.
 | 
						||
		[property, options] = [null, property];
 | 
						||
	}
 | 
						||
 | 
						||
	function Wikiapi_data_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		if (false && wiki_API.is_page_data(key)) {
 | 
						||
			// get entity (wikidata item) of page_data: key
 | 
						||
			// .page(key): 僅僅設定 .last_page,不會真的再獲取一次頁面內容。
 | 
						||
			wiki.page(key);
 | 
						||
		}
 | 
						||
		if (key.title && !key.site) {
 | 
						||
			// @see function wikidata_entity() @ CeL.application.net.wiki.data
 | 
						||
			// 確保引用到的是本 wiki session,不會引用到其他 site。
 | 
						||
			key = { ...key, site: this.site_name() };
 | 
						||
		}
 | 
						||
		// using wikidata_entity() → wikidata_datavalue()
 | 
						||
		wiki.data(key, property, (data_entity, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				setup_data_entity.call(wiki, data_entity);
 | 
						||
				resolve(data_entity);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_data_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias new_data_entity
 | 
						||
 * @description Create new entity or property
 | 
						||
 *
 | 
						||
 * @param {Object} data_to_modify	- Initial data.
 | 
						||
 * @param {Object} [options]		- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Object} new entity or property.
 | 
						||
 *
 | 
						||
 * @example <caption>Create new entity</caption>
 | 
						||
// <code>
 | 
						||
const new_entity = await wiki.new_data_entity({ labels: { en: "Evolution in Mendelian Populations" }, P698: "17246615", P932: "1201091" }, { new: 'item' });
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_new_data_entity(data_to_modify, options) {
 | 
						||
	function Wikiapi_new_data_entity_executor(resolve, reject) {
 | 
						||
		options = { new: 'item', ...options };
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.edit_data({}, options, (data_entity, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else if (data_to_modify) {
 | 
						||
				delete options.new;
 | 
						||
				//console.trace([data_entity, options]);
 | 
						||
				wiki.edit_data(data_entity, data_to_modify, options, (result, error) => {
 | 
						||
					if (error) {
 | 
						||
						reject(error);
 | 
						||
					} else if (false && options.retrieve_entity) {
 | 
						||
						// reget modified data
 | 
						||
						this.data(data_entity.id, options).then(resolve, reject);
 | 
						||
					} else {
 | 
						||
						//console.trace([data_entity, result]);
 | 
						||
						//data_entity.latest_result = result;
 | 
						||
						// data_entity: e.g.,
 | 
						||
						// {"type":"item","id":"Q123456","labels":{},"descriptions":{},"aliases":{},"claims":{},"sitelinks":{},"lastrevid":123456}
 | 
						||
						resolve(data_entity);
 | 
						||
					}
 | 
						||
				});
 | 
						||
			} else {
 | 
						||
				setup_data_entity.call(wiki, data_entity);
 | 
						||
				resolve(data_entity);
 | 
						||
			}
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_new_data_entity_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias SPARQL
 | 
						||
 * @description Query wikidata via SPARQL
 | 
						||
 *
 | 
						||
 * @param {Object} SPARQL		- SPARQL to query. Please test it on <a href="https://query.wikidata.org/">Wikidata Query Service</a> first.
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Array} query result of `SPARQL`.
 | 
						||
 *
 | 
						||
 * @example <caption>Get cats</caption>
 | 
						||
// <code>
 | 
						||
const wikidata_item_list = await wiki.SPARQL(`
 | 
						||
SELECT ?item ?itemLabel WHERE {
 | 
						||
  ?item wdt:P31 wd:Q146.
 | 
						||
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
 | 
						||
}
 | 
						||
`);
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Get specific DOI</caption>
 | 
						||
// <code>
 | 
						||
// for case-insensitive DOI
 | 
						||
const wikidata_item_list = await wiki.search('haswbstatement:' + JSON.stringify('P356=10.1371/journal.pone.0029797'), { namespace: 0 });
 | 
						||
//wikidata_item_list.map(item => item.title)
 | 
						||
 | 
						||
// for case-sensitive DOI
 | 
						||
const wikidata_item_list = await wiki.SPARQL(`
 | 
						||
SELECT ?doi ?item ?itemLabel WHERE {
 | 
						||
	VALUES ?doi { "10.1371/JOURNAL.PONE.0029797" }
 | 
						||
	?item wdt:P356 ?doi.
 | 
						||
	SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
 | 
						||
}`, {
 | 
						||
	// options.API_URL: custom SPARQL endpoint
 | 
						||
	API_URL: ''
 | 
						||
});
 | 
						||
//wikidata_item_list.id_list()
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_SPARQL(SPARQL, options) {
 | 
						||
	function Wikiapi_SPARQL_executor(resolve, reject) {
 | 
						||
		wiki_API.SPARQL(SPARQL, (result, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(result);
 | 
						||
			}
 | 
						||
		}, this.append_session_to_options(options));
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_SPARQL_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 *
 | 
						||
 * @example <caption>get list of [[w:en:Category:Chemical_elements]]</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
let list = await wiki.categorymembers('Chemical elements');
 | 
						||
console.log(list);
 | 
						||
// Working on multiple pages
 | 
						||
await wiki.for_each_page(
 | 
						||
	// {Array} title liat / page data list
 | 
						||
	list,
 | 
						||
	page_data => {
 | 
						||
		// ...
 | 
						||
	});
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>get pages transcluding {{w:en:Periodic table}}</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
let list = await wiki.embeddedin('Template:Periodic table');
 | 
						||
console.log(list);
 | 
						||
// </code>
 | 
						||
 */
 | 
						||
 | 
						||
// Warning: Won't throw if title is not existed!
 | 
						||
// @inner
 | 
						||
function Wikiapi_list(list_type, title, options) {
 | 
						||
	function Wikiapi_list_executor(resolve, reject) {
 | 
						||
		options = CeL.setup_options(options);
 | 
						||
		// const wiki = this[KEY_wiki_session];
 | 
						||
		wiki_API.list(title, (list/* , target, options */) => {
 | 
						||
			// console.trace(list);
 | 
						||
			if (list.error) {
 | 
						||
				reject(list.error);
 | 
						||
			} else {
 | 
						||
				resolve(list);
 | 
						||
			}
 | 
						||
		}, this.append_session_to_options({
 | 
						||
			type: list_type,
 | 
						||
			// namespace: '0|1',
 | 
						||
			...options
 | 
						||
		}));
 | 
						||
 | 
						||
		/**
 | 
						||
		 * <code>
 | 
						||
 | 
						||
		// method 2: 使用循環取得資料版:
 | 
						||
		wiki.cache({
 | 
						||
			// Do not write cache file to disk.
 | 
						||
			cache: false,
 | 
						||
			type: list_type,
 | 
						||
			list: title
 | 
						||
		}, (list, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(list);
 | 
						||
			}
 | 
						||
		},
 | 
						||
			// default options === this
 | 
						||
			//{ namespace : '0|1' }
 | 
						||
			options);
 | 
						||
 | 
						||
		// NG: 不應使用單次版
 | 
						||
		wiki[list_type](title, (list, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(list);
 | 
						||
			}
 | 
						||
		}, {
 | 
						||
				limit: 'max', ...options
 | 
						||
			});
 | 
						||
 | 
						||
		</code>
 | 
						||
		 */
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_list_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// functions for several kinds of lists
 | 
						||
function Wikiapi_for_each(type, title, for_each, options) {
 | 
						||
	return Wikiapi_list.call(this, type, title, {
 | 
						||
		for_each,
 | 
						||
		...options
 | 
						||
	});
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias category_tree
 | 
						||
 * @description Get structural category tree with sub-categories of <code>root_category</code>. This is powerful than categorymembers. Get sub-categories with {@link Wikiapi.KEY_subcategories}.
 | 
						||
 *
 | 
						||
 * @param {String} root_category	- category name
 | 
						||
 * @param {Object} [options]		- options to run this function.
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Array} category_tree.
 | 
						||
 *
 | 
						||
 * @example <caption>Checking if [[Category:Countries in North America]] including [[Mexico]].</caption>
 | 
						||
// <code>
 | 
						||
const enwiki = new Wikiapi('en');
 | 
						||
const page_list = await enwiki.category_tree('Countries in North America', 1);
 | 
						||
assert(page_list.some(page_data => page_data.title === 'United States'), 'list category tree: [[Category:Countries in North America]] must includes [[United States]]');
 | 
						||
assert('Mexico' in page_list[Wikiapi.KEY_subcategories], 'list category tree: [[Category:Mexico]] is a subcategory of [[Category:Countries in North America]]');
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Get all sub-categories of [[Category:Echinodermata]] with depth=2.</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi('commons');
 | 
						||
const all_sub_categories = (await wiki.category_tree('Echinodermata', { depth: 2, cmtype: 'subcat', get_flated_subcategories: true })).flated_subcategories;
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_category_tree(root_category, options) {
 | 
						||
	function Wikiapi_category_tree_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		// using wiki_API.prototype.category_tree
 | 
						||
		wiki.category_tree(root_category, (list, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(list);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_category_tree_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * export key for subcategory 子分類 used in {@link Wikiapi#category_tree}
 | 
						||
 *
 | 
						||
 * @example
 | 
						||
// <code>
 | 
						||
const KEY_subcategories = Wikiapi.KEY_subcategories;
 | 
						||
// </code>
 | 
						||
 */
 | 
						||
Wikiapi.KEY_subcategories = wiki_API.KEY_subcategories;
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias search
 | 
						||
 * @description search pages include <code>key</code>
 | 
						||
 *
 | 
						||
 * @param {String} key			- key to search
 | 
						||
 * @param {Object} [options]	- options to run this function.
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Array} page_list.
 | 
						||
 *
 | 
						||
 * @example <caption>search pages include key: 霍金</caption>
 | 
						||
// <code>
 | 
						||
const zhwikinews = new Wikiapi('zh.wikinews');
 | 
						||
const page_list = await zhwikinews.search('"霍金"');
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_search(key, options) {
 | 
						||
	function Wikiapi_search_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		// using wiki_API.search
 | 
						||
		wiki.search(key, (list, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(list);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_search_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias redirects_root
 | 
						||
 * @description Get redirects target of <code>title</code>.
 | 
						||
 * 
 | 
						||
 * @param {String} title		- page title
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {String} page title or {Object} page data
 | 
						||
 *
 | 
						||
 * @example <caption>Get redirects target of [[WP:SB]]</caption>
 | 
						||
// <code>
 | 
						||
const redirects_taregt = await enwiki.redirects_root('WP:SB', { get_page_data: true });
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_redirects_root(title, options) {
 | 
						||
	function Wikiapi_redirects_root_executor(resolve, reject) {
 | 
						||
		// const wiki = this[KEY_wiki_session];
 | 
						||
		// using wiki_API.redirects_root
 | 
						||
		wiki_API.redirects_root(title, (_title, page_data, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else if (options && options.get_page_data) {
 | 
						||
				page_data.query_title = title;
 | 
						||
				resolve(page_data);
 | 
						||
			} else {
 | 
						||
				resolve(_title);
 | 
						||
			}
 | 
						||
		}, this.append_session_to_options(options));
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_redirects_root_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias redirects_here
 | 
						||
 * @description Get all pages redirects to <code>title</code>.
 | 
						||
 * 
 | 
						||
 * @param {String} title		- page title
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Array} redirect_list
 | 
						||
 *
 | 
						||
 * @example <caption>Get all pages redirects to [[Wikipedia:Sandbox]]</caption>
 | 
						||
// <code>
 | 
						||
const redirects_list = await enwiki.redirects_here('Wikipedia:Sandbox');
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_redirects_here(title, options) {
 | 
						||
	function Wikiapi_redirects_here_executor(resolve, reject) {
 | 
						||
		// const wiki = this[KEY_wiki_session];
 | 
						||
		// using wiki_API.redirects_here
 | 
						||
		wiki_API.redirects_here(title, (root_page_data, redirect_list, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				//console.trace(root_page_data);
 | 
						||
				//console.trace(redirect_list);
 | 
						||
				//console.assert(!redirect_list || redirect_list === root_page_data.redirect_list);
 | 
						||
				resolve(redirect_list || root_page_data);
 | 
						||
			}
 | 
						||
		}, this.append_session_to_options({
 | 
						||
			// Making .redirect_list[0] the redirect target.
 | 
						||
			include_root: true,
 | 
						||
			...options
 | 
						||
		}));
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_redirects_here_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias register_redirects
 | 
						||
 * @description register page alias. usually used for templates
 | 
						||
 * 
 | 
						||
 * @param {Array|String} page_title_list	- list of page titles
 | 
						||
 * @param {Object} [options]				- options to run this function.
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents the operations are done.
 | 
						||
 *
 | 
						||
 * @example <caption>Register template redirects and get tokens of the templates.</caption>
 | 
						||
// <code>
 | 
						||
const wiki_session = new Wikiapi;
 | 
						||
// e.g., await wiki_session.register_redirects(['Section link', 'Broken anchors'], { namespace: 'Template' });
 | 
						||
await wiki_session.register_redirects([template_name_1, template_name_2, template_name_3], { namespace: 'Template' });
 | 
						||
 | 
						||
// ...
 | 
						||
 | 
						||
const page_data = await wiki_session.page(page_title);
 | 
						||
// {Array} parsed page content 頁面解析後的結構。
 | 
						||
const parsed = page_data.parse();
 | 
						||
 | 
						||
parsed.each('Template:' + template_name_1, function (token, index, parent) {
 | 
						||
	// ...
 | 
						||
});
 | 
						||
 | 
						||
parsed.each('template', function (token, index, parent) {
 | 
						||
	if (wiki_session.is_template(template_name_1, token)) {
 | 
						||
		// ...
 | 
						||
		return;
 | 
						||
	}
 | 
						||
	if (wiki_session.is_template(template_name_2, token)) {
 | 
						||
		// ...
 | 
						||
		return;
 | 
						||
	}
 | 
						||
 | 
						||
	// alternative method:
 | 
						||
	switch (wiki_session.redirect_target_of(token)) {
 | 
						||
		case wiki_session.redirect_target_of(template_name_1):
 | 
						||
			break;
 | 
						||
		case wiki_session.redirect_target_of(template_name_2):
 | 
						||
			break;
 | 
						||
		case wiki_session.redirect_target_of(template_name_3):
 | 
						||
			break;
 | 
						||
	}
 | 
						||
});
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_register_redirects(page_title_list, options) {
 | 
						||
	function Wikiapi_register_redirects_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.register_redirects(page_title_list, (redirect_list, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				// console.trace( redirect_list);
 | 
						||
				resolve(redirect_list);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_register_redirects_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias upload
 | 
						||
 * @description Upload specified local file to the target wiki.
 | 
						||
 *
 | 
						||
 * @param {Object} file_data	- Upload configurations.<br />
 | 
						||
 * Warning: When you are update a file, only the file content will changed. The <code>comment</code> will only show in the file page. The <code>text</code>, ... till <code>categories</code> will <em>all ignored</em>. If you want to update the content of file page, please consider <code>Variable_Map</code> as mentioned in the sample code.<br />
 | 
						||
{<br />
 | 
						||
<ul>
 | 
						||
<li><code>file_path</code>: string - Local path.</li>
 | 
						||
<li><code>media_url</code>: string - URL path. Alternative to <code>file_path</code>.</li>
 | 
						||
<li><code>comment</code>: string - Upload comment.</li>
 | 
						||
 | 
						||
<li><code>text</code>: string or {Object} - Either {String}wikitext to fill the file's page,<br />
 | 
						||
 or {Object}parameters of <a href="https://commons.wikimedia.org/wiki/Template:Information" target="_blank">{{Information}}</a>:<br />
 | 
						||
{<br />
 | 
						||
<ul>
 | 
						||
<li><code>description</code>: string - File description.</li>
 | 
						||
<li><code>date</code>: date string - YYYY-MM-DD, e.g., <code>new Date()</code> || <code>'2021-01-01'</code>.</li>
 | 
						||
<li><code>source_url</code>: string - Source where the file comes from, typically an URL.</li>
 | 
						||
<li><code>author</code>: string - Author's name or username in wikicode, e.g., URL or <code>'[[User:Yoda|Yoda]]'</code>.</li>
 | 
						||
<li><code>permission</code>: string - License and other usage limitations and warnings, e.g., <code>'{{cc-by-sa-2.5}}'</code>.</li>
 | 
						||
<li><code>other_versions</code>: string - Wikicode links to files with very similar content or derived files.</li>
 | 
						||
<li><code>other_fields</code>: string - Additional table fields added on the bottom of the template.</li>
 | 
						||
</ul>
 | 
						||
}
 | 
						||
</li>
 | 
						||
 | 
						||
<li><code>license</code>: array of strings - License under which the file is uploaded, e.g., <code>['{{cc-by-sa-2.5}}']</code>.</li>
 | 
						||
<li><code>additional_text</code>: string - Additional wikitext to place before <code>categories</code>.</li>
 | 
						||
<li><code>categories</code>: array of strings - Categories for this file, e.g., <code>['[[Category:test images]]']</code>.</li>
 | 
						||
 | 
						||
<li><code>ignorewarnings</code>: boolean - Set to 1 will overwrite existing files.</li>
 | 
						||
</ul>
 | 
						||
}<br />
 | 
						||
<br />
 | 
						||
See <a href="https://github.com/kanasimi/CeJS/blob/master/application/net/wiki/edit.js" target="_blank">edit.js</a> and search for <q>file_data</q> for other <code>file_data</code> options.
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {String} result of MediaWiki API
 | 
						||
 *
 | 
						||
 * @example <caption><span id="example__Upload file / media">Upload file / media</span></caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
await wiki.login('user', 'password', 'test');
 | 
						||
// Upload a local file directly:
 | 
						||
//let result = await wiki.upload({ file_path: '/local/file/path', comment: '', text: '' || {description: '', ...} });
 | 
						||
let result = await wiki.upload({
 | 
						||
	file_path: '/local/file/path', comment: '',
 | 
						||
	filename: 'Will set via .file_path or .media_url if not settled.',
 | 
						||
	description: '', date: new Date() || '2021-01-01', source_url: 'https://github.com/kanasimi/wikiapi', author: '[[User:user]]', permission: '{{cc-by-sa-2.5}}', other_versions: '', other_fields: '',
 | 
						||
	license: ['{{cc-by-sa-2.5}}'], categories: ['[[Category:test images]]'],
 | 
						||
	bot: 1, tags: "tag1|tag2",
 | 
						||
	// To overwrite existing file
 | 
						||
	ignorewarnings: 1,
 | 
						||
});
 | 
						||
// Upload file from URL:
 | 
						||
result = await wiki.upload({ media_url: 'https://media.url/name.jpg', comment: '', text: '' });
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Upload file and then update content of file page</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
await wiki.login('user', 'password', 'test');
 | 
						||
 | 
						||
const variable_Map = new CeL.wiki.Variable_Map();
 | 
						||
variable_Map.set('description', '...');
 | 
						||
//variable_Map.set('date', '...');
 | 
						||
// ...
 | 
						||
//variable_Map.set('other_fields', '...');
 | 
						||
 | 
						||
let result = await wiki.upload({
 | 
						||
	file_path: '/local/file/path',
 | 
						||
	// The <code>comment</code> will only show in the file page when updating file. It is read-only and cannot be modified.
 | 
						||
	comment: '',
 | 
						||
 | 
						||
	// <code>CeL.wiki.Variable_Map</code> is used to update content when update pages or files. It will insert comments around the value, prevent others from accidentally editing the text that will be overwritten.
 | 
						||
	// <code>description</code> till <code>other_fields</code> will be auto-setted as values assigned above.
 | 
						||
	// The code to do the conversion is in <code>wiki_API.upload</code> @ https://github.com/kanasimi/CeJS/blob/master/application/net/wiki/edit.js
 | 
						||
	// There are some examples: https://github.com/kanasimi/wikibot/blob/master/routine/20181016.import_earthquake_shakemap.js https://github.com/kanasimi/wikibot/blob/master/routine/20190629.import_hurricane_track_maps.js
 | 
						||
	// More examples to use <code>CeL.wiki.Variable_Map</code>: https://github.com/kanasimi/wikibot/blob/master/routine/20191129.check_language_convention.js
 | 
						||
	variable_Map,
 | 
						||
	// When set .variable_Map, after successful update, the content of file page will be auto-updated too.
 | 
						||
 | 
						||
	// To overwrite existing file
 | 
						||
	ignorewarnings: 1,
 | 
						||
});
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_upload(file_data) {
 | 
						||
	// 2021/3/25 renamed from old name: Wikiapi_upload_file(),
 | 
						||
	// Wikiapi_upload_file_executor()
 | 
						||
	function Wikiapi_upload_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.upload(file_data, (result, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(result);
 | 
						||
			}
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_upload_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias download
 | 
						||
 * @description Download file to local path.
 | 
						||
 *
 | 
						||
 * @param {String} file_title	- file title starts with "File:"
 | 
						||
 * @param {Object} [options]	- options to run this function. Refer to example codes.
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents [ {Object}file informations ]
 | 
						||
 *
 | 
						||
 * @example <caption>Download original file / media to current directory.</span></caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi('commons');
 | 
						||
await wiki.download('File:Example.svg');
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @example <caption>Download file / media with options</span></caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi('commons');
 | 
						||
 | 
						||
// Download non-vector version of .svg
 | 
						||
await wiki.download('File:Example.svg', { width: 80 });
 | 
						||
 | 
						||
// Change width / height
 | 
						||
await wiki.download('File:Example.png', {
 | 
						||
	file_name: 'example.png', directory: '/tmp/',
 | 
						||
	// reget and overwrite existed file.
 | 
						||
	reget: true,
 | 
						||
	width: 80,// height: 80
 | 
						||
});
 | 
						||
 | 
						||
// Download all files from a (Commons) category and its subcategories WITH directory structure.
 | 
						||
const file_data_list = await wiki.download('Category:name', {
 | 
						||
	directory: './',
 | 
						||
	max_threads: 4,
 | 
						||
	// depth of categories
 | 
						||
	depth: 4,
 | 
						||
	// Only download files with these formats.
 | 
						||
	//download_derivatives : ['wav', 'mp3', 'ogg'],
 | 
						||
	// Warning: Will skip downloading if there is no new file!
 | 
						||
	download_derivatives : 'mp3',
 | 
						||
	// A function to filter result pages. Return `true` if you want to keep the element.
 | 
						||
	page_filter(page_data) {
 | 
						||
		return page_data.title.includes('word');
 | 
						||
	}
 | 
						||
});
 | 
						||
 | 
						||
// Download all files from a (Commons) category WITHOUT directory structure.
 | 
						||
for (const page_data of await wiki.categorymembers('Category:name', { namespace: 'File' })) {
 | 
						||
	try {
 | 
						||
		//if (wiki.is_namespace(page_data, 'File'))
 | 
						||
		const file_data = await wiki.download(page_data, { directory: './' });
 | 
						||
	} catch (e) { console.error(e); }
 | 
						||
}
 | 
						||
// also
 | 
						||
const categorymembers = await wiki.categorymembers('Category:name', { namespace: 'File' });
 | 
						||
const file_data_list = await wiki.download(categorymembers, { directory: './', no_category_tree: true });
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_download(file_title, options) {
 | 
						||
	function Wikiapi_download_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.download(file_title, options, (result, error) => {
 | 
						||
			if (error) {
 | 
						||
				// return result.error_titles
 | 
						||
				reject(result && result.error_titles && result || error);
 | 
						||
			} else {
 | 
						||
				resolve(result);
 | 
						||
			}
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_download_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias for_each_page
 | 
						||
 * @description Edit / process pages listing in <code>page_list</code>. Will get the content of multiple pages at once to save transmission times. 一次取得多個頁面內容,以節省傳輸次數。
 | 
						||
 * 
 | 
						||
 * @param {Array} page_list			- title list or page_data list
 | 
						||
 * @param {Function} for_each_page	- processor for each page. for_each_page(page_data with contents)
 | 
						||
 * @param {Object} [options]		- options to run this function. Refer to example codes.
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents the operations are done.
 | 
						||
 *
 | 
						||
 * @example <caption>read / edit multiple pages</caption>
 | 
						||
// <code>
 | 
						||
const enwiki = new Wikiapi('en');
 | 
						||
const link_from = await wiki.redirects_here('ABC');
 | 
						||
await wiki.for_each_page(link_from, page_data => {
 | 
						||
	// Return `Wikiapi.skip_edit` if you just want to get the page data.
 | 
						||
	return Wikiapi.skip_edit;
 | 
						||
	return 'You may also modify page contents for each page';
 | 
						||
}, {
 | 
						||
	// The options below are sample, not default configuration.
 | 
						||
 | 
						||
	// denotes we do not edit pages
 | 
						||
	no_edit: true,
 | 
						||
 | 
						||
	// Only needed if you want to modify page.
 | 
						||
	summary: 'test edit',
 | 
						||
	// Allow content to be emptied. 允許內容被清空。白紙化。
 | 
						||
	allow_empty: true,
 | 
						||
	tags: 'bot trial',
 | 
						||
	// prevent creating new pages
 | 
						||
	// Throw an error if the page doesn't exist.
 | 
						||
	// 若頁面不存在/已刪除,則產生錯誤。
 | 
						||
	nocreate: 1,
 | 
						||
	// denotes this is a bot edit. 標記此編輯為機器人編輯。
 | 
						||
	bot: 1,
 | 
						||
	minor: 1,
 | 
						||
 | 
						||
	// options to get page revisions
 | 
						||
	page_options: { redirects: 1, rvprop: 'ids|content|timestamp|user' }
 | 
						||
 | 
						||
	// <code>.for_each_page()</code> will generate a report. It can be written to the specified page.
 | 
						||
	log_to: 'log to this page',
 | 
						||
	// no warning messages on console. e.g., hide "wiki_API_page: No contents: [[title]]" messages
 | 
						||
	no_warning: true,
 | 
						||
	// no warning messages and debug messages on console
 | 
						||
	no_message: true,
 | 
						||
});
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_for_each_page(page_list, for_each_page, options) {
 | 
						||
	function Wikiapi_for_each_page_executor(resolve, reject) {
 | 
						||
		options = typeof options === 'string' ? { summary: options } : CeL.setup_options(options);
 | 
						||
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		const append_to_this = Array.isArray(for_each_page) && for_each_page[1];
 | 
						||
		if (Array.isArray(for_each_page))
 | 
						||
			for_each_page = for_each_page[0];
 | 
						||
		// console.trace(for_each_page);
 | 
						||
		const work_config = {
 | 
						||
			log_to: null,
 | 
						||
			no_message: options.no_edit,
 | 
						||
 | 
						||
			...options,
 | 
						||
 | 
						||
			//is_async_each: CeL.is_async_function(for_each_page),
 | 
						||
			each: [function each(page_data/* , messages, config */) {
 | 
						||
				set_page_data_attributes(page_data, wiki);
 | 
						||
 | 
						||
				return for_each_page.apply(this, arguments);
 | 
						||
			}, append_to_this],
 | 
						||
			// Run after all list items (pages) processed.
 | 
						||
			last(error) {
 | 
						||
				// this === options
 | 
						||
				// console.trace('last(error)');
 | 
						||
				// console.error(error);
 | 
						||
				// console.trace('Wikiapi_for_each_page_executor finish:');
 | 
						||
				// console.log(options);
 | 
						||
 | 
						||
				// 提早執行 resolve(), reject() 的話,可能導致後續的程式碼 `options.last`
 | 
						||
				// 延後執行,程式碼順序錯亂。
 | 
						||
				if (typeof options.last === 'function')
 | 
						||
					options.last.call(this, error);
 | 
						||
				if (error) {
 | 
						||
					if (options.throw_error) {
 | 
						||
						reject(error);
 | 
						||
						return;
 | 
						||
					}
 | 
						||
					console.error(error);
 | 
						||
				}
 | 
						||
				resolve(this);
 | 
						||
			}
 | 
						||
		};
 | 
						||
 | 
						||
		wiki.work(work_config, page_list);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_for_each_page_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias convert_Chinese
 | 
						||
 * @description convert text to traditional Chinese / simplified Chinese.
 | 
						||
 * 
 | 
						||
 * @param {String|Array|Object} text	- text or objects to convert. Will convert to {String} using JSON.stringify().
 | 
						||
 * @param {Object} [options]			- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents the converted text.
 | 
						||
 *
 | 
						||
 * @example <caption>繁簡轉換</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi('en');
 | 
						||
await wiki.convert_Chinese('中国', { uselang: 'zh-hant' });
 | 
						||
await wiki.convert_Chinese('中國', { uselang: 'zh-hans' });
 | 
						||
await wiki.convert_Chinese(['繁體', '簡體'], { uselang: 'zh-hans' });
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_convert_Chinese(text, options) {
 | 
						||
	function Wikiapi_convert_Chinese(resolve, reject) {
 | 
						||
		if (typeof options === 'string') {
 | 
						||
			options = { uselang: options };
 | 
						||
		}
 | 
						||
		const site_name = this.site_name({ get_all_properties: true });
 | 
						||
		// node.js v12.22.7: Cannot use "?."
 | 
						||
		if (site_name && site_name.language === 'zh') {
 | 
						||
			// 不用再重新造出一個實體。
 | 
						||
			options = this.append_session_to_options(options);
 | 
						||
		}
 | 
						||
 | 
						||
		// using wiki_API.search
 | 
						||
		wiki_API.convert_Chinese(text, (text, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(text);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_convert_Chinese.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
// May only test in the [https://tools.wmflabs.org/ Wikimedia Toolforge]
 | 
						||
function Wikiapi_run_SQL(SQL, for_each_row/* , options */) {
 | 
						||
	function Wikiapi_run_SQL_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		function run_callback() {
 | 
						||
			wiki.SQL_session.SQL(SQL, (error, rows/* , fields */) => {
 | 
						||
				if (error) {
 | 
						||
					reject(error);
 | 
						||
				} else {
 | 
						||
					rows.forEach(for_each_row);
 | 
						||
				}
 | 
						||
			});
 | 
						||
			resolve();
 | 
						||
		}
 | 
						||
		if (wiki.SQL_session) {
 | 
						||
			run_callback();
 | 
						||
			return;
 | 
						||
		}
 | 
						||
		wiki.SQL_session = new wiki_API.SQL((error, rows, fields) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				run_callback();
 | 
						||
			}
 | 
						||
		}, wiki);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_run_SQL_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
function Wikiapi_setup_layout_elements(options) {
 | 
						||
	function Wikiapi_setup_layout_elements_executor(resolve, reject) {
 | 
						||
		// const wiki = this[KEY_wiki_session];
 | 
						||
		wiki_API.setup_layout_elements(resolve, this.append_session_to_options(options));
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_setup_layout_elements_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias get_featured_content
 | 
						||
 * @description Get featured content.
 | 
						||
 * 
 | 
						||
 * @param {String|Object} [options]	- options to run this function.
 | 
						||
 *            {String}type (FFA|GA|FA|FL)
 | 
						||
 *            || {type,on_conflict(FC_title, {from,to})}
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents {Object} featured content data hash
 | 
						||
 *
 | 
						||
 * @example <caption>Get featured content of current wiki site.</caption>
 | 
						||
// <code>
 | 
						||
// MUST including wiki.featured_content first to get featured content!
 | 
						||
CeL.run('application.net.wiki.featured_content');
 | 
						||
 | 
						||
// ...
 | 
						||
 | 
						||
const FC_data_hash = await wiki.get_featured_content();
 | 
						||
console.assert(FC_data_hash === wiki.FC_data_hash);
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_get_featured_content(options) {
 | 
						||
	if (!options || !options.type) {
 | 
						||
		const session = this;
 | 
						||
		return Wikiapi_get_featured_content.default_types
 | 
						||
			.reduce((promise, type) => promise.then(Wikiapi_get_featured_content.bind(session, { ...options, type })), Promise.resolve());
 | 
						||
		if (false) {
 | 
						||
			let promise = Promise.resolve();
 | 
						||
			Wikiapi_get_featured_content.default_types.forEach(type => {
 | 
						||
				promise = promise.then(Wikiapi_get_featured_content.bind(session, { ...options, type }));
 | 
						||
			});
 | 
						||
			return promise;
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	function Wikiapi_get_featured_content_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.get_featured_content(options, FC_data_hash => {
 | 
						||
			try {
 | 
						||
				this.FC_data_hash = FC_data_hash;
 | 
						||
				resolve(FC_data_hash);
 | 
						||
			} catch (e) {
 | 
						||
				reject(e);
 | 
						||
			}
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_get_featured_content_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
Wikiapi_get_featured_content.default_types = 'FFA|GA|FA|FL'.split('|');
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias site_name
 | 
						||
 * @description Get site name / project name of this {Wikiapi}.
 | 
						||
 * 
 | 
						||
 * @param {String} [language]	- language code of wiki session
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {String}site name
 | 
						||
 * 
 | 
						||
 * @example <caption>Get site name of {Wikiapi}.</caption>
 | 
						||
// <code>
 | 
						||
console.log(Wikiapi.site_name('zh', { get_all_properties: true }));
 | 
						||
 | 
						||
const wiki = new Wikiapi('en');
 | 
						||
console.assert(wiki.site_name() === 'enwiki');
 | 
						||
console.log(wiki.site_name({ get_all_properties: true }));
 | 
						||
console.assert(wiki.site_name({ get_all_properties: true }).language === 'en');
 | 
						||
// </code>
 | 
						||
 * 
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_site_name(language, options) {
 | 
						||
	return wiki_API.site_name(language, options);
 | 
						||
}
 | 
						||
 | 
						||
Wikiapi.site_name = Wikiapi_site_name;
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
// administration functions 管理功能。
 | 
						||
 | 
						||
/**
 | 
						||
 * @alias delete
 | 
						||
 * @description delete page
 | 
						||
 *
 | 
						||
 * @param {String} title		- page title
 | 
						||
 * @param {Object} [options]	- options to run this function
 | 
						||
 *
 | 
						||
 * @returns {Promise} Promise object represents response of delete.
 | 
						||
 *
 | 
						||
 * @example <caption>delete page [[Page to delete]]</caption>
 | 
						||
// <code>
 | 
						||
const testwiki = new Wikiapi('test');
 | 
						||
await testwiki.delete('Page to delete', { reason: 'test' });
 | 
						||
// { title: 'Aaaaaaa', reason: 'test', logid: 346223 }
 | 
						||
// </code>
 | 
						||
 *
 | 
						||
 * @memberof Wikiapi.prototype
 | 
						||
 */
 | 
						||
function Wikiapi_delete(title, options) {
 | 
						||
	function Wikiapi_delete_executor(resolve, reject) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		// using wiki_API.delete
 | 
						||
		wiki.page(title).delete(options, (response, error) => {
 | 
						||
			if (error) {
 | 
						||
				reject(error);
 | 
						||
			} else {
 | 
						||
				resolve(response);
 | 
						||
			}
 | 
						||
		}, options);
 | 
						||
	}
 | 
						||
 | 
						||
	return new Promise(Wikiapi_delete_executor.bind(this));
 | 
						||
}
 | 
						||
 | 
						||
// --------------------------------------------------------
 | 
						||
// exports
 | 
						||
 | 
						||
Object.assign(Wikiapi.prototype, {
 | 
						||
	append_session_to_options(options) {
 | 
						||
		// Object.assign({ [KEY_SESSION]: wiki }, options)
 | 
						||
		// return { ...options, [KEY_SESSION]: this[KEY_wiki_session] };
 | 
						||
		return wiki_API.add_session_to_options(this[KEY_wiki_session], options);
 | 
						||
	},
 | 
						||
 | 
						||
	site_name(options) {
 | 
						||
		return Wikiapi_site_name(this[KEY_wiki_session], options);
 | 
						||
	},
 | 
						||
	login: Wikiapi_login,
 | 
						||
 | 
						||
	query: Wikiapi_query,
 | 
						||
	page: Wikiapi_page,
 | 
						||
	tracking_revisions: Wikiapi_tracking_revisions,
 | 
						||
	edit_page: Wikiapi_edit_page,
 | 
						||
	/**
 | 
						||
	 * @description edits content of target page.<br />
 | 
						||
	 * <em>MUST using after {@link Wikiapi#page}!</em><br />
 | 
						||
	 * Note: for multiple pages, you should use {@link Wikiapi#for_each_page}.<br />
 | 
						||
	 * Note: The function will check sections of [[User talk:user name/Stop]] if somebody tells us needed to stop edit. See <a href="https://zh.wikipedia.org/wiki/User:Cewbot/Stop">mechanism to stop operations</a>.
 | 
						||
	 * 
 | 
						||
	 * @param {String|Function} content	- 'wikitext page content' || page_data => 'wikitext'
 | 
						||
	 * @param {Object} [options]		- options to run this function. e.g., { summary: '', bot: 1, nocreate: 1, minor: 1 }
 | 
						||
	 * 
 | 
						||
	 * @returns {Promise} Promise object represents {Object} result of MediaWiki API
 | 
						||
	 *
 | 
						||
	 * @memberof Wikiapi.prototype
 | 
						||
	 */
 | 
						||
	edit(content, options) {
 | 
						||
		return this.edit_page(null, content, options);
 | 
						||
	},
 | 
						||
	move_to: Wikiapi_move_to,
 | 
						||
	move_page: Wikiapi_move_page,
 | 
						||
	purge: Wikiapi_purge,
 | 
						||
	/**
 | 
						||
	 * @description Listen to page modification. 監視最近更改的頁面。<br />
 | 
						||
	 * wrapper for {@link wiki_API}#listen
 | 
						||
	 *
 | 
						||
	 * @param {Function} listener	- function(page_data) { return quit_listening; }
 | 
						||
	 * @param {Object} [options]	- options to run this function. e.g., { summary: '', bot: 1, nocreate: 1, minor: 1 }
 | 
						||
	 *
 | 
						||
	 * @example <caption>listen to new edits</caption>
 | 
						||
// <code>
 | 
						||
const wiki = new Wikiapi;
 | 
						||
wiki.listen(function for_each_row() {
 | 
						||
	// ...
 | 
						||
}, {
 | 
						||
	// 檢查的延遲時間。
 | 
						||
	delay: '2m',
 | 
						||
	filter: function filter_row(row) {
 | 
						||
		// row is the same format as page_data
 | 
						||
	},
 | 
						||
	// also get diff
 | 
						||
	with_diff: { LCS: true, line: true },
 | 
						||
	// only for articles (0:main namespace) and talk pages
 | 
						||
	namespace: '0|talk',
 | 
						||
});
 | 
						||
// </code>
 | 
						||
	 *
 | 
						||
	 * @memberof Wikiapi.prototype
 | 
						||
	 */
 | 
						||
	listen(listener, options) {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		wiki.listen(listener, options);
 | 
						||
	},
 | 
						||
 | 
						||
	category_tree: Wikiapi_category_tree,
 | 
						||
	search: Wikiapi_search,
 | 
						||
 | 
						||
	redirects_root: Wikiapi_redirects_root,
 | 
						||
	// Warning: 採用 wiki_API.redirects_here(title) 才能追溯重新導向的標的。
 | 
						||
	// wiki.redirects() 無法追溯重新導向的標的!
 | 
						||
	redirects_here: Wikiapi_redirects_here,
 | 
						||
	register_redirects: Wikiapi_register_redirects,
 | 
						||
 | 
						||
	upload: Wikiapi_upload,
 | 
						||
	download: Wikiapi_download,
 | 
						||
 | 
						||
	get_featured_content: Wikiapi_get_featured_content,
 | 
						||
 | 
						||
	for_each_page: Wikiapi_for_each_page,
 | 
						||
 | 
						||
	for_each: Wikiapi_for_each,
 | 
						||
 | 
						||
	delete: Wikiapi_delete,
 | 
						||
 | 
						||
	data: Wikiapi_data,
 | 
						||
	new_data_entity: Wikiapi_new_data_entity,
 | 
						||
	SPARQL: Wikiapi_SPARQL,
 | 
						||
 | 
						||
	convert_Chinese: Wikiapi_convert_Chinese,
 | 
						||
 | 
						||
	run_SQL: Wikiapi_run_SQL,
 | 
						||
 | 
						||
	setup_layout_elements: Wikiapi_setup_layout_elements,
 | 
						||
});
 | 
						||
 | 
						||
// wrapper for properties
 | 
						||
for (const property_name of ('task_configuration|latest_task_configuration').split('|')) {
 | 
						||
	Object.defineProperty(Wikiapi.prototype, property_name, {
 | 
						||
		get() {
 | 
						||
			const wiki = this[KEY_wiki_session];
 | 
						||
			return wiki[property_name];
 | 
						||
		}
 | 
						||
	});
 | 
						||
}
 | 
						||
 | 
						||
// wrapper for sync functions
 | 
						||
for (const function_name of ('namespace|remove_namespace|is_namespace|to_namespace|is_talk_namespace|to_talk_page|talk_page_to_main|normalize_title|redirect_target_of|aliases_of_page|is_template'
 | 
						||
	// CeL.run('application.net.wiki.featured_content');
 | 
						||
	// [].map(wiki.to_talk_page.bind(wiki))
 | 
						||
	+ '|get_featured_content_configurations').split('|')) {
 | 
						||
	Wikiapi.prototype[function_name] = function wrapper() {
 | 
						||
		const wiki = this[KEY_wiki_session];
 | 
						||
		return wiki[function_name].apply(wiki, arguments);
 | 
						||
	};
 | 
						||
}
 | 
						||
 | 
						||
// @see get_list.type @
 | 
						||
// https://github.com/kanasimi/CeJS/blob/master/application/net/wiki/list.js
 | 
						||
for (const type of wiki_API.list.type_list) {
 | 
						||
	// Cannot use `= (title, options) {}` !
 | 
						||
	// arrow function expression DO NOT has this, arguments, super, or
 | 
						||
	// new.target keywords.
 | 
						||
	Wikiapi.prototype[type] = function (title, options) {
 | 
						||
		const _this = this;
 | 
						||
		/**
 | 
						||
		 * @example <code>
 | 
						||
		
 | 
						||
		const page_list = await wiki.embeddedin(template_name, options);
 | 
						||
		await page_list.each((page_data) => { }, options);
 | 
						||
 | 
						||
		 * </code>
 | 
						||
		 */
 | 
						||
		return Wikiapi_list.call(this, type, title, options)
 | 
						||
			.then((page_list) => {
 | 
						||
				// console.log(page_list);
 | 
						||
				page_list.each = Wikiapi_for_each_page.bind(_this, page_list);
 | 
						||
				return page_list;
 | 
						||
			});
 | 
						||
	};
 | 
						||
}
 | 
						||
 | 
						||
module.exports = Wikiapi;
 | 
						||
 | 
						||
// export default Wikiapi;
 |