/**
 * @name CeL function for MediaWiki (Wikipedia / 維基百科)
 * 
 * @fileoverview 本檔案包含了 MediaWiki 自動化作業用的程式庫,主要用於編寫[[維基百科:機器人]]
 *               ([[WP:{{{name|{{int:Group-bot}}}}}|{{{name|{{int:Group-bot}}}}}]])。
 * 
 * TODO:
wiki_API.work() 遇到 Invalid token 之類問題,中途跳出 abort 時,無法紀錄。應將紀錄顯示於 console 或 local file。
wiki_API.page() 整合各 action=query 至單一公用 function。
[[mw:Manual:Pywikibot/zh]]
[[mw:Help:OAuth]]
https://www.mediawiki.org/wiki/OAuth/Owner-only_consumers
https://meta.wikimedia.org/wiki/Steward_requests/Miscellaneous#OAuth_permissions
[[m:Special:OAuthConsumerRegistration/propose]] (using an owner-only consumers) get (consumer_key, consumer_secret, access_token, access_secret)
Wikimedia REST API
https://www.mediawiki.org/wiki/RESTBase
https://zh.wikipedia.org/w/index.php?title=title&action=history&hilight=123,456
-{zh-hans:访问;zh-hant:訪問;zh-tw:瀏覽}-量
https://wikitech.wikimedia.org/wiki/Analytics/PageviewAPI
https://en.wikipedia.org/wiki/Wikipedia:Pageview_statistics
https://dumps.wikimedia.org/other/pagecounts-raw/
https://tools.wmflabs.org/pageviews
https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-raw
https://meta.wikimedia.org/wiki/Research:Page_view
WikiData Remote editor
http://tools.wmflabs.org/widar/
get user infomation:
https://www.mediawiki.org/w/api.php?action=help&modules=query%2Busers
https://zh.wikipedia.org/w/api.php?action=query&format=json&list=users&usprop=blockinfo|groups|implicitgroups|rights|editcount|registration|emailable|gender|centralids|cancreate&usattachedwiki=zhwiki&ususers=username|username
https://www.mediawiki.org/w/api.php?action=help&modules=query%2Busercontribs
https://zh.wikipedia.org/w/api.php?action=query&format=json&list=usercontribs&uclimit=1&ucdir=newer&ucprop=ids|title|timestamp|comment|parsedcomment|size|sizediff|flags|tags&ucuser=username
對Action API的更改,請訂閱
https://lists.wikimedia.org/pipermail/mediawiki-api-announce/
雙重重定向/重新導向/転送
特別:二重リダイレクト
Special:DoubleRedirects
Special:BrokenRedirects
https://www.mediawiki.org/w/api.php?action=help&modules=query%2Bquerypage
[[mw:User:Duplicatebug/API Overview/action]]
https://test.wikipedia.org/w/api.php?action=query&list=querypage&qppage=DoubleRedirects&qplimit=max
gadgets 小工具 [[Wikipedia:Tools]], [[Category:Wikipedia scripts]], [[mw:ResourceLoader/Core modules]]
[[Special:MyPage/common.js]] [[使用說明:維基用戶腳本開發指南]]
// ---------------------------------------------------------
// https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.loader
mediaWiki.loader.load('https://kanasimi.github.io/CeJS/ce.js')
CeL.run('application.net.wiki');
CeL.wiki.page('Wikipedia:機器人',function(page_data){console.log(page_data);},{redirects:true,section:0})
// wikibits從2013年就棄用
// https://www.mediawiki.org/wiki/ResourceLoader/Legacy_JavaScript#wikibits.js
// NG: importScript('User:cewbot/*.js');
你可以在維基媒體的wiki網站URL最後增加?safemode=1來關閉你個人的CSS和JavaScript。範例:https://zh.wikipedia.org/wiki/文學?safemode=1。上面一行意思是你可以測試是否是你的使用者腳本或套件造成問題,而不必解除安裝。
 * 
 * @see https://github.com/siddharthvp/mwn
 */
// More examples: see /_test suite/test.js
// Wikipedia bots demo: https://github.com/kanasimi/wikibot
// JavaScript MediaWiki API for ECMAScript 2017+ :
// https://github.com/kanasimi/wikiapi
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
	// module name
	name : 'application.net.wiki',
	// .includes() @ CeL.data.code.compatibility
	// .between() @ CeL.data.native
	// .append() @ CeL.data.native
	require : 'data.code.compatibility.|data.native.'
	// (new Date).format('%4Y%2m%2d'), (new Date).format() @ CeL.data.date
	// optional 選用: .show_value() @ CeL.interact.DOM, CeL.application.debug
	// optional 選用: CeL.wiki.cache(): CeL.application.platform.nodejs.fs_mkdir()
	// optional 選用: CeL.wiki.traversal(): CeL.application.platform.nodejs
	// optional 選用: wiki_API.work(): gettext():
	// optional 選用: CeL.application.storage
	// CeL.application.locale.gettext()
	// CeL.date.String_to_Date(), Julian_day(), .to_millisecond(): CeL.data.date
	+ '|data.date.',
	// 設定不匯出的子函式。
	no_extend : '*',
	// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
	code : module_code
});
function module_code(library_namespace) {
	// requiring
	// --------------------------------------------------------------------------------------------
	// https://github.com/Microsoft/TypeScript/wiki/JSDoc-support-in-JavaScript
	/**
	 * web Wikipedia / 維基百科 用的 functions。
	 * 可執行環境: node.js, JScript。
	 * 
	 * TODO: new wiki_API(API_URL || login_options);
	 * wiki_session.login(user_name, password, API_URL);
	 * 
	 * @param {String}user_name
	 *            user name
	 * @param {String}password
	 *            user password
	 * @param {String}[API_URL]
	 *            language code or API Endpoint URL
	 * 
	 * @returns {wiki_API} wiki site API
	 * @template wiki_API
	 * 
	 * @constructor
	 */
	function wiki_API(user_name, password, API_URL) {
		if (!this || this.constructor !== wiki_API) {
			return wiki_API.query.apply(null, arguments);
		}
		// TODO: this.login(user_name, password, API_URL);
		var login_options;
		if (API_URL && typeof API_URL === 'object') {
			// session = new wiki_API(user_name, password, login_options);
			login_options = API_URL;
			API_URL = null;
		} else if (!API_URL && !password && user_name
				&& typeof user_name === 'object') {
			// session = new wiki_API(login_options);
			login_options = user_name;
			user_name = null;
			// console.log(login_options);
		} else {
			login_options = Object.create(null);
		}
		user_name = user_name || login_options.user_name;
		password = password || login_options.password;
		API_URL = API_URL || login_options.API_URL/* || login_options.project */;
		// console.trace([ user_name, password, API_URL ]);
		library_namespace.debug('URL of service endpoint: ' + API_URL
				+ ', default language: ' + wiki_API.language, 3, 'wiki_API');
		// action queue 佇列。應以 append,而非整個換掉的方式更改。
		this.actions = [];
		// @see wiki_API.prototype.next
		if (login_options.is_running) {
			// Is calling from wiki_API.login()
			// login 前便執行其他作業,可能導致 Session=deleted。 e.g., running
			// login_options.configuration_adapter() @ 20201008.fix_anchor.js
			if (typeof login_options.is_running === 'string')
				this.actions.unshift([ login_options.is_running ]);
			// 執行權交給 wiki_API.login()。
			this.running = true;
		}
		// 權杖
		this.token = {
			// lgusername
			lgname : user_name,
			// user_password
			lgpassword : password
		};
		// console.trace(API_URL);
		if (!API_URL && !('language' in this)
		// wikidata 不設定 language。
		&& !this[wiki_API.KEY_HOST_SESSION]) {
			API_URL = wiki_API.language;
			// 假若未設定 API_URL 或 user_name,那就不初始化。等 .login 才初始化。
			// 若想基本的初始化,最起碼必須設定 API_URL。
			login_options.need_initialize = password && user_name;
		} else if (!('need_initialize' in login_options)) {
			login_options.need_initialize = true;
		}
		if ('use_SQL' in login_options) {
			this.use_SQL = login_options.use_SQL;
		} else if (API_URL
		// assert: typeof API_URL === 'string'
		&& API_URL.includes('://')) {
			// assert: Not MediaWiki server. Is outer server.
			this.use_SQL = false;
		}
		// console.trace(API_URL);
		// setup session.
		if (API_URL) {
			// e.g., 'cmn'
			if (API_URL in wiki_API.language_code_to_site_alias)
				API_URL = wiki_API.language_code_to_site_alias[API_URL];
			wiki_API.setup_API_language(this /* session */, API_URL);
			wiki_API.setup_API_URL(this /* session */, API_URL);
		}
		[ 'site_name', 'data_API_URL', 'SPARQL_API_URL',
		// Must after wiki_API.setup_API_language()!
		'language' ]
		//
		.forEach(function(property) {
			if (property in login_options)
				this[property] = login_options[property];
		}, this);
		// console.trace(this);
		this.general_parameters = Object.clone(wiki_API.general_parameters);
		library_namespace.import_options(login_options,
		// @see CeL.application.net.wiki.namespace
		wiki_API.general_parameters_normalizer, this.general_parameters);
		if (library_namespace.is_WWW(true) && window.location
		// For non-authenticated requests, specify the value *. This
		// will cause the Access-Control-Allow-Origin header to be set,
		// but Access-Control-Allow-Credentials will be false and all
		// user-specific data will be restricted.
		&& this.general_parameters.origin !== '*') {
			var host;
			if (!window.location.host
			// e.g., locale file: window.location.host===""
			|| (host = new URL(this.API_URL).host)
					&& host !== window.location.host
					&& host !== this.general_parameters.origin) {
				library_namespace.warn([ 'wiki_API: ', {
					// gettext_config:{"id":"you-may-need-to-set-$1-=-$2"}
					T : [ 'You may need to set %1 = %2!',
					//
					'.origin', JSON.stringify(host) ]
				} ]);
			}
		}
		if (login_options.localStorage_prefix_key && wiki_API.has_storage) {
			// assert: typeof login_options.localStorage_prefix_key === 'string'
			// ||
			// typeof login_options.localStorage_prefix_key === 'number'
			this.localStorage_prefix = [ library_namespace.Class,
					wiki_API.site_name(this),
					login_options.localStorage_prefix_key, '' ]
			// '.'
			.join(library_namespace.env.module_name_separator);
		}
		// ------------------------------------------------
		// pre-loading functions
		// https://stackoverflow.com/questions/39007637/javascript-set-vs-array-performance
		// https://jsbench.me/3pkjlwzhbr/1
		// .API_parameters[modules.path].parameter_Map = parameter Map
		// @see get_API_parameters()
		this.API_parameters = Object.create(null);
		// wiki_session.redirects_data[redirect_from] = {String}redirect_to
		// = main page title without "Template:" prefix
		// @see CeL.application.net.wiki.task ,
		// CeL.application.net.wiki.namespace
		this.redirects_data = Object.create(null);
		if (login_options.need_initialize) {
			this.run_after_initializing = [];
			// 注意: new wiki_API() 後之操作,應該採 wiki_session.run()
			// 的方式,確保此時已經執行過 pre-loading functions @ function wiki_API():
			// wiki_session.siteinfo(), wiki_session.adapt_task_configurations()
			this.run(initialize_wiki_API, login_options);
		} else {
			// e.g.,
			// wiki = new CeL.wiki; ...; wiki.login(login_options);
		}
	}
	function initialize_wiki_API(options) {
		var session = this;
		// console.trace(session.actions);
		// console.trace(session.running);
		// if (session.API_URL)
		session.siteinfo(load_template_functions);
		// console.trace(session.actions);
		// console.trace(session.running);
		function load_template_functions() {
			// console.trace(session);
			// @see CeL.application.net.wiki.template_functions
			if (session.load_template_functions)
				session.load_template_functions(null,
				//
				adapt_task_configurations);
			else
				adapt_task_configurations();
		}
		function adapt_task_configurations() {
			// console.trace(options);
			if (options.task_configuration_page) {
				session.adapt_task_configurations(
						options.task_configuration_page,
						function(configuration) {
							// console.trace(configuration);
							if (options.configuration_adapter)
								options.configuration_adapter(configuration);
							initialization_complete();
						});
			} else {
				initialization_complete();
			}
		}
		function initialization_complete() {
			library_namespace.debug(wiki_API.site_name(session) + ': '
					+ '初始化程序登錄完畢。' + '添加之前登錄的 ' + session.actions.length
					+ ' 個程序到佇列中。', 1, 'initialization_complete');
			session.actions.append(session.run_after_initializing);
			delete session.run_after_initializing;
			// console.trace(session.actions);
		}
	}
	initialize_wiki_API.is_initializing_process = true;
	/**
	 * 檢查若 value 為 session。
	 * 
	 * @param value
	 *            value to test. 要測試的值。
	 * 
	 * @returns {Boolean} value 為 session。
	 */
	function is_wiki_API(value) {
		return value
				&& ((value instanceof wiki_API) || value.API_URL && value.token);
	}
	// ------------------------------------------------------------------------
	// export 導出.
	// @static
	Object.assign(wiki_API, {
		is_wiki_API : is_wiki_API
	});
	if (library_namespace.is_WWW(true) && typeof mw === 'object' && mw
			&& typeof mw.config === 'object'
			&& typeof mw.config.get === 'function'
			&& typeof mediaWiki === "object" && mediaWiki === mw) {
		wiki_API.mw_web_session = true;
	}
	// 等執行再包含入必須的模組。
	this.finish = function(name_space, waiting, sub_modules_to_full_module_path) {
		var sub_modules = [ 'namespace', 'parser', 'query', 'page',
				'page.Page', 'Flow', 'list', 'edit', 'task', 'parser.wikitext',
				'parser.section', 'parser.misc', 'parser.evaluate' ];
		// ------------------------------------------------------------------------
		// auto import SQL 相關函數 @ Toolforge。
		// function setup_wmflabs()
		// only for node.js.
		// https://wikitech.wikimedia.org/wiki/Help:Toolforge/FAQ#How_can_I_detect_if_I.27m_running_in_Cloud_VPS.3F_And_which_project_.28tools_or_toolsbeta.29.3F
		if (library_namespace.platform.nodejs) {
			/** {String}Wikimedia Toolforge name. CeL.wiki.wmflabs */
			wiki_API.wmflabs = require('fs').existsSync('/etc/wmflabs-project')
			// e.g., 'tools-bastion-05'.
			// if use `process.env.INSTANCEPROJECT`,
			// you may get 'tools' or 'tools-login'.
			&& (library_namespace.env.INSTANCENAME
			// 以 /usr/bin/jsub 執行時可得。
			// e.g., 'tools-exec-1210.eqiad.wmflabs'
			|| library_namespace.env.HOSTNAME || true);
		}
		if (wiki_API.wmflabs) {
			// import CeL.application.net.wiki.Toolforge
			sub_modules.push('Toolforge');
		}
		// --------------------------------------------------------------------
		// Essential dependency chain
		library_namespace.debug({
			T :
			// gettext_config:{"id":"load-the-main-functions-and-necessary-dependencies-to-operate-mediawiki"}
			'Load the main functions and necessary dependencies to operate MediaWiki.'
		}, 1, 'wiki_API');
		// library_namespace.set_debug(2);
		library_namespace.run(sub_modules_to_full_module_path(sub_modules),
		// The `wiki_API.mw_web_session` is a session that operates in a web
		// environment. For example, the Wikipedia widget.
		function() {
			if (wiki_API.mw_web_session) {
				wiki_API.mw_web_session = new wiki_API({
					API_URL :
					// mediaWiki.config.get('wgServer')
					location.origin
					// https://www.mediawiki.org/wiki/Manual:$wgScriptPath
					+ mediaWiki.config.get('wgScriptPath')
					// https://www.mediawiki.org/wiki/Manual:Api.php
					+ '/api.php',
					localStorage_prefix_key : 'mw_web_session'
				});
				// fill tokens
				for ( var token_name in mediaWiki.user.tokens.values) {
					wiki_API.mw_web_session.token[
					// 'csrfToken' → 'csrftoken'
					token_name.toLowerCase()]
					//
					= mediaWiki.user.tokens.values[token_name];
				}
				// 預設對所有網站會使用相同的 cookie
				// @see
				// https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.Api
			}
			library_namespace.debug({
				// gettext_config:{"id":"all-wiki-submodules-are-loaded"}
				T : 'All wiki submodules are loaded.'
			}, 1, 'wiki_API');
		}, waiting);
		return waiting;
	};
	return wiki_API;
}