/**
* @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;
}