cejs
Version:
A JavaScript module framework that is simple to use.
1,614 lines (1,430 loc) • 168 kB
JavaScript
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): task control
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2020/5/24 6:21:13 拆分自 CeL.application.net.wiki
*/
// More examples: see /_test suite/test.js
// Wikipedia bots demo: https://github.com/kanasimi/wikibot
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
// module name
name : 'application.net.wiki.task',
require : 'data.native.'
// for library_namespace.get_URL
+ '|application.net.Ajax.' + '|application.net.wiki.'
// load MediaWiki module basic functions
+ '|application.net.wiki.namespace.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki, KEY_SESSION = wiki_API.KEY_SESSION, KEY_CORRESPOND_PAGE = wiki_API.KEY_CORRESPOND_PAGE;
// @inner
var is_api_and_title = wiki_API.is_api_and_title, add_session_to_options = wiki_API.add_session_to_options;
var gettext = library_namespace.cache_gettext(function(_) {
gettext = _;
});
var
/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
NOT_FOUND = ''.indexOf('_');
// ------------------------------------------------------------------------
// get_data_key(), get_data_page()
function get_wikibase_key(id) {
if (!id)
return;
if (id[KEY_CORRESPOND_PAGE])
id = id[KEY_CORRESPOND_PAGE];
return id.site && id.title && id;
}
// check if session.last_data is usable, 非過期資料。
function last_data_is_usable(session) {
// When "servers are currently under maintenance", session.last_data is
// a string.
if (typeof session.last_data === 'object' && !session.last_data.error
// 若是session.last_data與session.last_page連動,必須先確認是否沒變更過session.last_page,才能當作cache、跳過重新擷取entity之作業。
&& (!(KEY_CORRESPOND_PAGE in session.last_data)
// assert:
// wiki_API.is_page_data(session.last_data[KEY_CORRESPOND_PAGE])
|| session.last_page === session.last_data[KEY_CORRESPOND_PAGE])) {
library_namespace.debug('Use cached data: [['
//
+ (KEY_CORRESPOND_PAGE in session.last_data
// may use wiki_API.title_link_of()
? session.last_page.id : session.last_data.id) + ']]', 1,
'last_data_is_usable');
return true;
}
}
// --------------------------------------------------------------------------------------------
// instance 實例相關函數。
// 在實例函數中,會依賴外部 promise 之後才繼續執行處設置監測點,以檢測是否需要手動執行 session.next()。
// e.g.,
// node 20201008.fix_anchor.js use_language=zh archives
function set_up_if_needed_run_next(run_next_status) {
run_next_status = Object.assign(run_next_status || Object.create(null),
// this: wiki session
{
// actions : this.actions.slice(),
// actions_length : this.actions.length,
// waiting_callback_result_relying_on_this : this.actions[
//
// wiki_API.KEY_waiting_callback_result_relying_on_this],
last_action : this && this.running && this.actions.length > 0
&& this.actions.at(-1)
});
return run_next_status;
}
// 檢查上次 session.set_up_if_needed_run_next() 之後是否有新添加的 actions。
function check_if_needed_run_next(run_next_status) {
if (!run_next_status)
return;
if (run_next_status.needed_run_next)
return true;
var last_action = run_next_status.last_action;
// console.trace(run_next_status);
// this: wiki session
var index_of_old_tail = last_action && this.actions.length > 0
&& this.actions.lastIndexOf(last_action) || undefined;
// console.trace([ index_of_old_tail, this.actions.length ]);
// 若有新添加的 actions,由於這些 actions 全被 push 進 queue,不會被執行到,
// 因此必須手動執行 this.next()。
if (index_of_old_tail >= 0 ? this.actions.length - index_of_old_tail > 1
: this.actions.length > 0) {
// console.trace(this.actions);
run_next_status.needed_run_next = true;
return true;
}
}
/**
* session.check_if_needed_run_next(run_next_status) 可以執行多次。
* session.check_and_run_next() 只能在 promise 之後馬上執行一次。
*
* e.g., Inside session instance functions:<code>
var run_next_status = session && session.set_up_if_needed_run_next();
// 某些可能會用到 session.* 並且回傳 {Promise} 的動作。
//...
promise = promise.then(successive_function_after_promise);
if (session)
session.check_and_run_next(run_next_status, promise);
// 直接跳出。之後會等 promise 出結果才繼續執行。
return;
</code>
*/
function check_and_run_next(run_next_status, promise, no_check) {
if (!run_next_status)
return;
if (!no_check)
this.check_if_needed_run_next(run_next_status);
if (!run_next_status.needed_run_next) {
return;
}
if (!promise)
promise = run_next_status.promise || Promise.resolve();
library_namespace.debug(
'新增了不會被執行到的 actions。手動執行 session.next(promise)。', 2,
'check_and_run_next');
// console.trace(promise);
// 重設 run_next_status 以供重複利用.
delete run_next_status.needed_run_next;
run_next_status = this.set_up_if_needed_run_next(run_next_status);
this.next(promise);
return run_next_status;
}
Object.assign(wiki_API.prototype, {
set_up_if_needed_run_next : set_up_if_needed_run_next,
check_if_needed_run_next : check_if_needed_run_next,
check_and_run_next : check_and_run_next
});
// ------------------------------------------------------------------------
/**
* Register promise relying on wiki session actions. 設定依賴於本 wiki_API action
* 的 promise。
*
* @param promise
* promise to set
*
* @example session.set_promise_relying(result);
*/
wiki_API.prototype.set_promise_relying = function set_promise_relying(
promise) {
// Promise.isPromise()
if (library_namespace.is_thenable(promise)
// no rely on wiki_API
// && !promise.not_relying_on_wiki_API
) {
// assert: promise 依賴於本 wiki_API action thread。
library_namespace.debug('設定依賴於本 wiki_API action 的 promise。', 3,
'set_promise_relying');
if (library_namespace.is_debug(3)) {
console.trace([ this.running, promise, this.actions ]);
// console.trace(promise);
}
this.actions.promise_relying = library_namespace
.is_thenable(this.actions.promise_relying) ? this.actions.promise_relying
.then(promise)
: promise;
return true;
}
};
wiki_API.prototype.test_promise_relying = function test_promise_relying() {
// this.actions.promise_relying is relying on this action.
// 為了偵測這些promise是否已fulfilled,必須先this.running,預防其他執行緒鑽空隙。
this.running = true;
var _this = this;
function status_handler(fulfilled, this_thenable) {
if (this_thenable !== _this.actions.promise_relying) {
library_namespace.debug(
'有其他執行緒鑽空隙,執行了 .set_promise_relying()。需要再檢測一次。', 3,
'test_promise_relying');
_this.test_promise_relying();
return;
}
if (fulfilled) {
delete _this.actions.promise_relying;
if (0 < _this.actions.length) {
// assert: other threads added _this.actions
// after library_namespace.status_of_thenable()
library_namespace.debug('有其他執行緒鑽空隙,設定了 .actions。', 3,
'test_promise_relying');
_this.next();
} else {
library_namespace
.debug(
'依賴於本 wiki_API action 的 promise 皆已 fulfilled。本 action 結束。',
3, 'test_promise_relying');
_this.running = false;
}
return;
}
// incase session.next() will wait for this.actions.promise_relying
// calling back if CeL.is_thenable(result).
// e.g., await wiki.for_each_page(need_check_redirected_list,
// ... @ 20200122.update_vital_articles.js
// So we need to run `session.next()` manually.
// await wiki.for_each_page(need_check_redirected_list,
// ... @ 20200122.update_vital_articles.js:
// 從 function work_page_callback() return 之後,會回到 function
// wiki_API_edit()。
// `this_thenable` 會等待 push 進 session.actions 的
// this.page(this_slice, main_work, page_options),
// 但 return 的話,會保持 session.running === true &&
// session.actions.length > 0
// 並且 abort。執行不到 this_thenable.then()。
if (0 < _this.actions.length) {
// 有些 promise 依賴於本 wiki_API action,假如停下來的話將會導致直接 exit跳出。
if (false) {
console
.trace('test_promise_relying: Calling wiki_API.prototype.next() '
+ [ _this.running, _this.actions.length ]);
}
_this.next();
} else {
if (false) {
console.trace('test_promise_relying: No .actions left! '
+ [ _this.running, _this.actions.length ]);
}
// delete _this.actions.promise_relying;
_this.running = false;
}
}
library_namespace.status_of_thenable(this.actions.promise_relying,
status_handler);
};
// ------------------------------------------------------------------------
/**
* 系統內定繁簡轉換無法獲得的字元,屬於一對多繁簡轉換詞彙。<br />
* 例如 "模板:規範控製" 可重定向到 "模板:規范控制",然而系統內定的繁簡轉換無法從"控制"獲得"控製"。因此在設定
* redirects_variants_patterns 時必須手動加入 "製"→"制"。
*
* 這邊基本上只記錄用在模板的文字。
*
* @see [[w:zh:簡繁轉換一對多列表]]。
*/
var char_variants_hash = {
// 维基大学计划 ⭠ 維基大學計畫
划 : [ '畫' ],
// 規范控制 ⭠ 規範控製
制 : [ '製' ]
};
function add_char_variants(char_Array) {
// char_Array = char_Array.clone();
for ( var char_to_check in char_variants_hash) {
if (char_Array.includes(char_to_check)) {
var char_list_to_append = char_variants_hash[char_to_check];
char_list_to_append.forEach(function(char_to_append) {
if (!char_Array.includes(char_to_append)) {
if (false) {
console.trace([ char_to_check, char_to_append,
char_Array ]);
}
char_Array.push(char_to_append);
}
});
break;
}
}
return char_Array;
}
// ------------------------------------------------------------------------
/** 代表欲自動設定 options.page_to_edit */
wiki_API.VALUE_set_page_to_edit = true;
var KEY_first_char_list = typeof Symbol === 'function' ? Symbol('first_char_list')
: '\0first_char_list';
// @inner
function set_page_to_edit(options, page_data, error, page_title) {
if (!options
|| options.page_to_edit !== wiki_API.VALUE_set_page_to_edit) {
return;
}
if (page_title) {
if (!options.page_title_to_edit) {
options.page_title_to_edit = page_title;
} else if (page_title !== options.page_title_to_edit) {
library_namespace.info('set_page_to_edit: '
+ '跳過改變頁面 .page_title_to_edit 的標題: '
+ wiki_API.title_link_of(options.page_title_to_edit)
+ '→' + wiki_API.title_link_of(page_title));
}
}
if (options.multi === true && Array.isArray(page_data)
&& page_data.length === 1) {
page_data = page_data[0];
}
if (options.page_title_to_edit
&& wiki_API.title_link_of(options.page_title_to_edit) !== wiki_API
.title_link_of(page_data.original_title
|| page_data.title)) {
library_namespace.info('set_page_to_edit: '
+ '所取得頁面 .page_to_edit 的標題改變: '
+ wiki_API.title_link_of(options.page_title_to_edit)
+ '→'
+ wiki_API.title_link_of(page_data.original_title
|| page_data.title));
if (!page_data.title) {
console.trace(page_title, page_data);
}
// console.trace(options);
}
options.page_to_edit = page_data;
options.last_page_error = error;
// options.last_page_options = options;
}
// @inner
wiki_API.KEY_waiting_callback_result_relying_on_this = typeof Symbol === 'function' ? Symbol('waiting callback_result_relying_on_this')
: '\0waiting callback_result_relying_on_this';
/**
* 設定工作/添加新的工作。
*
* 注意: 每個 callback 皆應在最後執行 session.next()。
*
* 警告: 若 callback throw,可能導致工作中斷,不會自動復原,得要以 wiki.next() 重起工作。
*
* 工作原理: 每個實體會hold住一個queue ({Array}this.actions)。 當設定工作時,就把工作推入佇列中。
* 另外內部會有另一個行程負責依序執行每一個工作。
*
* @see wiki_API_prototype_method() @ CeL.application.net.wiki.list
*/
wiki_API.prototype.next = function next(callback_result_relying_on_this) {
if (this.actions[wiki_API.KEY_waiting_callback_result_relying_on_this]) {
// assert: 此時為 session.next() 中執行 callback。
// callback_result_relying_on_this 執行中應該只能 push 進
// session.actions,不可執行 session.next()!
// e.g., 'edit_structured_data' 之 callback 直接採用
// _this.next(next[4], data, error);
// 若 next[4] 會再次 call session.edit_structured_data(),
// 可能造成執行 callback_result_relying_on_this 後,
// 到 'structured_data' 跳出準備 wiki_API.data(),
// 回到 callback_result_relying_on_this 主程序
// 就直接跑到 'edit_structured_data' 這邊來,結果選了錯誤的 this.last_page。
// e.g., check_structured_data() @ CeL.application.net.wiki.edit
callback_result_relying_on_this = Array.prototype.slice
.call(arguments);
callback_result_relying_on_this.unshift('run');
this.actions.push(callback_result_relying_on_this);
library_namespace
.debug(
'在 callback_result_relying_on_this 中 call this.next() 並且 waiting callback 而跳出。為避免造成多執行序,將執行權交予 call callback_result_relying_on_this() 之母執行序,子執行序這邊跳出。',
1, 'wiki_API.prototype.next');
// console.trace(this.actions.length);
return;
}
// ------------------------------------------------
// 標註當前執行的執行緒正在 running。
this.running = true;
var _this = this;
// 所有呼叫 instance 功能的 callback 全部推至 this.actions queue,
// 之後當前執行緒會繼續執行。藉以維持單一執行緒。
if (callback_result_relying_on_this) {
var process_callback = function process_callback(callback) {
// assert: this.running === true
if (typeof callback !== 'function') {
_this.set_promise_relying(callback);
return;
}
// 標註正在執行 callback()。
// Will run this.next() after callback() finished.
_this.actions[wiki_API.KEY_waiting_callback_result_relying_on_this] = true;
// assert: this.running === true
try {
callback = callback
// _this.next(callback, ...callback_arguments);
.apply(_this, process_callback.args);
} catch (e) {
// Error.stackTraceLimit = Infinity;
if (library_namespace.env.has_console)
console.error(e);
else
library_namespace.error(e);
}
// assert: this.running === true
delete _this.actions[wiki_API.KEY_waiting_callback_result_relying_on_this];
if (callback)
_this.set_promise_relying(callback);
};
process_callback.args = Array.prototype.slice.call(arguments, 1);
// console.trace(process_callback.args);
if (Array.isArray(callback_result_relying_on_this))
callback_result_relying_on_this.forEach(process_callback);
else
process_callback(callback_result_relying_on_this);
// free / reset
process_callback = callback_result_relying_on_this = null;
}
// assert: false ===
// library_namespace.is_thenable(callback_result_relying_on_this)
// ------------------------------------------------------------------------------
this.running = 0 < this.actions.length;
if (!this.running) {
if (library_namespace.is_thenable(this.actions.promise_relying)) {
this.test_promise_relying();
} else {
// this.thread_count = 0;
// delete this.current_action;
library_namespace.debug('The queue is empty.', 2,
'wiki_API.prototype.next');
// console.trace(this.actions);
}
return;
}
// 繼續執行接下來的行動。
// ------------------------------------------------
library_namespace.debug('剩餘 ' + this.actions.length + ' action(s)', 2,
'wiki_API.prototype.next');
if (library_namespace.is_debug(3)) {
console
.trace([
this.running,
this.actions.length,
this.actions.promise_relying,
this.actions[wiki_API.KEY_waiting_callback_result_relying_on_this],
next ]);
}
if (library_namespace.is_debug(3)
// .show_value() @ interact.DOM, application.debug
&& library_namespace.show_value) {
library_namespace.show_value(this.actions.slice(0, 10));
}
var next = this.actions.shift();
// 不改動 next。
var type = next[0], list_type;
if (// type in get_list.type
wiki_API.list.type_list.includes(type)) {
list_type = type;
type = 'list';
}
// this.current_action = next;
// console.trace(next);
if (library_namespace.is_debug(3)) {
library_namespace.debug(
//
'處理 ' + (this.token.lgname ? this.token.lgname + ' ' : '') + '['
//
+ next.map(function(arg) {
// for function
var message;
if (arg && arg.toString) {
message = arg.toString();
} else {
try {
message = JSON.stringify(arg);
} catch (e) {
// message = String(arg);
message = library_namespace.is_type(arg);
}
}
return message && message.slice(0, 80);
}) + ']', 1, 'wiki_API.prototype.next');
}
// ------------------------------------------------
// 若需改變,需同步更改 wiki_API.prototype.next.methods
switch (type) {
// setup options
case 'set_URL':
// next[1] : callback
wiki_API.setup_API_URL(this /* session */, next[1]);
this.next();
break;
case 'set_language':
// next[1] : callback
wiki_API.setup_API_language(this /* session */, next[1]);
this.next();
break;
case 'set_data':
// 設定 this.data_session。
// using @inner
// setup_data_session(session, callback, API_URL, password, force)
wiki_API.setup_data_session(this /* session */,
// 確保 data_session login 了才執行下一步。
function() {
// console.trace(_this);
// console.trace(_this.data_session);
// next[1] : callback of set_data
_this.next(next[1]);
}, next[2], next[3], next[4]);
break;
// ------------------------------------------------
// account
case 'login':
library_namespace.debug(
'正 log in 中,當 login 後,會自動執行 .next(),處理餘下的工作。', 2,
'wiki_API.prototype.next');
// rollback
this.actions.unshift(next);
break;
case 'logout':
// 結束
// next[1] : callback
wiki_API.logout(this /* session */, next[1]);
// this.next();
break;
// ------------------------------------------------
// page access
case 'query':
console.trace('use query');
throw new Error('Please use .query_API() instead of only .query()!');
library_namespace
.error('Please use .query_API() instead of only .query()!');
case 'query_API':
// wiki_API.query(post_data, callback, options)
if (next[4] === undefined && library_namespace.is_Object(next[3])
&& next[3].post_data_only) {
// shift arguments
next[4] = next[3];
next[3] = next[1];
next[1] = '';
}
// wiki_API.query(action, callback, post_data, options)
wiki_API.query(next[1], function query_API_callback(data, error) {
// 再設定一次,預防有執行期中間再執行的情況。
// e.g., wiki.query_api(action,function(){wiki.page();})
// 注意: 這動作應該放在callback()執行完後設定。
// next[2] : callback
_this.next(next[2], data, error);
}, next[3],
// next[4] : options
add_session_to_options(this, next[4]));
break;
case 'siteinfo':
// wiki.siteinfo(options, callback)
// wiki.siteinfo(callback)
if (typeof next[1] === 'function' && !next[2]) {
// next[1] : callback
next[2] = next[1];
next[1] = null;
}
wiki_API.siteinfo(add_session_to_options(this, next[1]), function(
data, error) {
// next[2] : callback
// run next action
_this.next(next[2], data, error);
});
break;
case 'page':
// console.trace(next);
// this.page(page data, callback, options);
if (next[1] === null) {
// console.trace(this.actions);
}
// Done @ wiki_API_prototype_methods()
// @ CeL.application.net.wiki.list
if (false && library_namespace.is_Object(next[2]) && !next[3]) {
// 直接輸入 options,未輸入 callback。
next.splice(2, 0, null);
}
/** {Object}取得 next[1] 這個頁面的時候使用的 revisions parameters。 */
var revisions_parameters = wiki_API.is_page_data(next[1])
&& next[1].revisions_parameters || Object.create(null);
// → 此法會採用所輸入之 page data 作為 this.last_page,不再重新擷取 page。
if (wiki_API.is_page_data(next[1])
//
&& (!next[3]
// 檢查是否非 cached 的內容。
|| next[3].rvprop === revisions_parameters.rvprop
// 重複編輯同一個頁面?
&& next[3].page_to_edit !== wiki_API.VALUE_set_page_to_edit)
// 必須有頁面內容,要不可能僅有資訊。有時可能已經擷取過卻發生錯誤而沒有頁面內容,此時依然會再擷取一次。
&& (wiki_API.content_of.has_content(next[1],
//
next[3] && next[3].rvlimit - 1)
// 除非剛剛才取得,同一個執行緒中不需要再度取得內容。
|| next[3] && next[3].allow_missing
// 確認真的是不存在的頁面。預防一次擷取的頁面內容太多,或者其他出錯情況,實際上沒能成功取得頁面內容,
// next[1].revisions:[]
&& (('missing' in next[1]) || ('invalid' in next[1])))) {
library_namespace.debug('採用所輸入之 '
+ wiki_API.title_link_of(next[1])
+ ' 作為 this.last_page。', 2, 'wiki_API.prototype.next');
// console.trace(next, this.last_page_error);
// console.trace(this.actions);
this.last_page = next[1];
// console.trace(next[1]);
set_page_to_edit(next[3], next[1], this.last_page,
this.last_page_error);
// next[2] : callback
this.next(next[2], next[1]);
break;
}
// free
revisions_parameters = null;
if (this.last_page_title === next[1]
&& this.last_page_options === next[3]) {
// 這必須防範改動頁面之後重新取得。
library_namespace.debug('採用前次的回傳以避免重複取得頁面。', 2,
'wiki_API.prototype.next');
// console.trace('採用前次的回傳以避免重複取得頁面: ' + next[1]);
set_page_to_edit(next[3], next[1], this.last_page_error);
// next[2] : callback
this.next(next[2], this.last_page,
// @see "合併取得頁面的操作"
this.last_page_error);
break;
}
// ----------------------------------
if (typeof next[1] === 'function') {
// this.page(callback): callback(last_page)
set_page_to_edit(next[3], this.last_page, this.last_page_error);
// next[1] : callback
this.next(next[1], this.last_page, this.last_page_error);
break;
}
// ----------------------------------
if (false) {
console.trace(_this.thread_count + '/' + _this.actions.length
+ 'actions: '
+ _this.actions.slice(0, 9).map(function(action) {
return action[0];
}));
// console.log(next);
}
// 準備擷取新的頁面。為了預防舊的頁面資料被誤用,因此將此將其刪除。
// 例如在 .edit() 的callback中再呼叫 .edit():
// wiki.page().edit(,()=>wiki.page().edit(,))
delete this.last_page;
delete this.last_page_title;
delete this.last_page_options;
delete this.last_page_error;
// console.trace(next[1]);
// next[3] : options
if (next[3]) {
if (next[3].page_to_edit === wiki_API.VALUE_set_page_to_edit) {
// page-edit 組合式操作。設定等待先前的取得頁面操作中。
next[3].waiting_for_previous_combination_operation = true;
if (!next[3].page_title_to_edit) {
// e.g., log_options @ wiki_API.prototype.work
// Only section : 'new'
next[3].page_title_to_edit = next[1];
}
}
if (library_namespace.is_debug(0)) {
// 設定個僅 debug 用、無功能的註記。
next[3].actions_when_fetching_page = [ next ]
.append(this.actions);
next[3].actions_when_fetching_page.only_for_debug = true;
}
next[3].page_fetching = Date.now();
}
// this.page(title, callback, options)
// next[1] : title
// next[3] : options
// [ {String}API_URL, {String}title or {Object}page_data ]
wiki_API.page(is_api_and_title(next[1]) ? next[1] : [ this.API_URL,
next[1] ],
//
function wiki_API_next_page_callback(page_data, error) {
if (false && next[3].page_fetching === 'unshift_edit')
console.trace([ page_data, error, _this.actions ]);
if (false) {
if (Array.isArray(page_data)) {
console.trace(page_data.length
+ ' pages get: '
+ page_data.slice(0, 10).map(
function(page_data) {
return page_data.title;
}));
} else {
console.trace([ page_data, error ]);
}
}
// assert: 當錯誤發生,例如頁面不存在/已刪除,依然需要模擬出 page_data。
// 如此才能執行 .page().edit()。
_this.last_page
// 正常情況。確保this.last_page為單頁面。需要使用callback以取得result。
= Array.isArray(page_data) ? page_data[0] : page_data;
// 用於合併取得頁面的操作。 e.g.,
// node 20201008.fix_anchor.js use_language=zh archives
_this.last_page_title = next[1];
_this.last_page_options = next[3];
_this.last_page_error = error;
// next[3] : options
set_page_to_edit(next[3], page_data, error, next[1]);
var original_title = next[1];
// assert: typeof original_title === 'string'
var next_action = _this.actions[0];
if (false && next_action
//
&& next_action[0] === 'edit'
// next_action[2]: options
&& typeof next_action[2] === 'object'
//
&& (!next_action[2].page_to_edit
//
|| next_action[2].page_to_edit
//
=== wiki_API.VALUE_set_page_to_edit)) {
if (!next_action[2].page_title_to_edit
//
|| next_action[2].page_title_to_edit === original_title) {
if (original_title !== page_data.title) {
library_namespace.info(
//
'wiki_API.prototype.next.page: ' + '所取得頁面的標題改變: '
+ wiki_API.title_link_of(original_title)
+ '→'
+ wiki_API.title_link_of(page_data.title));
}
// 手動指定要編輯的頁面。避免多執行續打亂 wiki.last_page。
next_action[2].page_to_edit = page_data;
next_action[2].page_title_to_edit = original_title;
next_action[2].last_page_options = next[3];
next_action[2].last_page_error = error;
} else {
library_namespace.error(
//
'wiki_API.prototype.next.page: ' + '無法設定欲編輯的頁面資訊: '
+ wiki_API.title_link_of(original_title) + '→'
+ wiki_API.title_link_of(page_data.title)
+ ' 不等於 ' + wiki_API.title_link_of(
//
next_action[2].page_title_to_edit));
console.trace([ next, next_action ]);
}
}
if (!page_data) {
// console.trace([ '' + next[2], page_data, error ]);
}
// console.trace(_this.actions);
// next[2] : callback
_this.next(next[2], page_data, error);
},
// next[3] : options
add_session_to_options(this, next[3]));
break;
case 'tracking_revisions':
if (typeof next[3] === 'object') {
// shift arguments
next[4] = next[3];
next[3] = null;
}
wiki_API.tracking_revisions(next[1], next[2], function(revision,
error) {
_this.next(next[3], revision, error);
},
// next[4] : options
add_session_to_options(this, next[4]));
break;
case 'parse':
// e.g., wiki.page('title', options).parse(callback, options);
if (library_namespace.is_Object(next[1]) && !next[2]) {
// 直接輸入 options,未輸入 callback。
next.splice(1, 0, null);
}
// next[2] : options
var parsed = wiki_API.parser(this.last_page,
add_session_to_options(this, next[2])).parse();
// next[3] : callback
this.next(next[1], parsed);
break;
case 'purge':
if (typeof next[1] === 'string' || typeof next[1] === 'number'
|| wiki_API.is_page_data(next[1])) {
// purge() 可以直接輸入頁面,不必先 .page('Title')
// wiki.purge('Title', callback, options)
// wiki.purge('Title', options)
// wiki.purge(pageid, callback, options)
// wiki.purge('pageid|pageid', options)
} else {
// wiki.page('Title').purge()
// wiki.page('Title').purge(callback, options)
// wiki.page('Title').purge(options)
next.splice(1, 0, this.last_page);
}
if (library_namespace.is_Object(next[2]) && !next[3]) {
// 直接輸入 options,未輸入 callback。
next.splice(2, 0, null);
}
// next: [ 'purge', pages, callback, options ]
if (!next[1]) {
library_namespace
.warn('wiki_API.prototype.next.purge: No page inputed!');
// next[3] : callback
this.next(next[3], undefined, 'no page');
} else {
wiki_API.purge([ this.API_URL, next[1] ],
//
function wiki_API_next_purge_callback(purge_pages, error) {
// next[2] : callback
_this.next(next[2], purge_pages, error);
},
// next[3] : options
add_session_to_options(this, next[3]));
}
break;
case 'redirect_to':
// this.redirect_to(page data, callback, options);
if (library_namespace.is_Object(next[2]) && !next[3]) {
// 直接輸入 options,未輸入 callback。
next.splice(2, 0, null);
}
// this.redirect_to(title, callback, options)
// next[1] : title
// next[3] : options
// [ {String}API_URL, {String}title or {Object}page_data ]
wiki_API.redirect_to(is_api_and_title(next[1]) ? next[1] : [
this.API_URL, next[1] ],
//
function wiki_API_next_redirect_to_callback(redirect_data,
page_data, error) {
// next[2] : callback
_this.next(next[2], redirect_data, page_data, error);
},
// next[3] : options
add_session_to_options(this, next[3]));
break;
case 'list':
// get_list(). e.g., 反向連結/連入頁面。
// next[1] : 大部分是 page title,
// 但因為有些方法不需要用到頁面標題(recentchanges,allusers)因此對於這一些方法需要特別處理。
if (typeof next[1] === 'function' && typeof next[2] !== 'function') {
next.splice(1, 0, '');
}
// 注意: arguments 與 get_list() 之 callback 連動。
wiki_API[list_type]([ this.API_URL, next[1] ],
//
function wiki_API_next_list_callback(pages, error) {
// [ page_data ]
_this.last_pages = pages;
if (typeof next[2] === 'function') {
// 注意: arguments 與 get_list() 之 callback 連動。
callback_result_relying_on_this
// next[2] : callback(pages, error)
= next[2].call(_this, pages, error);
} else if (next[2] && next[2].each) {
// next[2] : 當作 work,處理積存工作。
if (pages) {
_this.work(next[2]);
} else {
// 只有在本次有處理頁面時,才繼續下去。
library_namespace.info('無頁面可處理(已完成?),中斷跳出。');
}
}
_this.next(callback_result_relying_on_this);
},
// next[3] : options
add_session_to_options(this, next[3]));
break;
case 'random_categorymembers':
// console.trace(next);
wiki_API.random_categorymembers(next[1], function() {
callback_result_relying_on_this
// next[2] : callback(pages, target, options)
= next[2].apply(_this, arguments);
_this.next(callback_result_relying_on_this);
},
// next[3] : options
add_session_to_options(this, next[3]));
break;
// ------------------------------------------------------
// case 'category_tree':
// @see wiki_API.prototype.category_tree @ application.net.wiki.list
// register page alias. usually used for templates
case 'register_redirects':
// wiki.register_redirects(page_title_list, callback, options)
// wiki.register_redirects(page_title_list, options)
if (library_namespace.is_Object(next[2]) && !next[3]) {
// 未設定/不設定 callback
// shift arguments
next.splice(2, 0, undefined);
}
var redirects_data;
if (library_namespace.is_Object(next[1])) {
// temp
redirects_data = next[1];
if (!next[3].update_page_name_hash) {
library_namespace
.info('wiki_API.prototype.next.register_redirects: '
+ '查詢傳入引數 Object 的 value 而非 key。');
}
next[1] = Object.values(next[1]);
}
// next[3] : options
next[3] = Object.assign({
// 取得定向的終點。
redirects : 1,
// [KEY_SESSION]
session : this,
// Making .redirect_list[0] the redirect target.
include_root : true,
// converttitles: 1,
// multiple pages
multi : Array.isArray(next[1]) && next[1].length > 1
}, next[3]);
if (next[3].update_page_name_hash === true && redirects_data) {
// wiki.register_redirects({key:'page_name'},callback,{update_page_name_hash:true});
next[3].update_page_name_hash = redirects_data;
}
// next[1]: page_title
if (next[3].namespace)
next[1] = this.to_namespace(next[1], next[3].namespace);
// console.trace(next[1]);
next[1] = this.normalize_title(next[1]);
if (!next[1]) {
library_namespace.error([
//
'wiki_API.prototype.next.register_redirects: ', {
// gettext_config:{"id":"invalid-title-$1"}
T : [ 'Invalid title: %1',
//
wiki_API.title_link_of(next[1]) ]
} ]);
// next[2] : callback(root_page_data, error)
this.next(next[2], next[1], new Error(gettext(
// gettext_config:{"id":"invalid-title-$1"}
'Invalid title: %1', wiki_API.title_link_of(next[1]))));
break;
}
redirects_data = next[3].register_target || this.redirects_data;
if (next[3].reget) {
} else if (Array.isArray(next[1])) {
next[1] = next[1].filter(function(page_title) {
if (false && redirects_data !== _this.redirects_data
// Copy data: 這會漏掉 page_data.original_title,
// page_data.redirect_from
&& (page_title in _this.redirects_data)) {
redirects_data[page_title]
//
= _this.redirects_data[page_title];
}
return page_title && !(page_title in redirects_data)
//
&& !(page_title in _this.nonexistent_pages);
}).unique();
if (next[1].length === 0) {
// next[2] : callback(root_page_data, error)
this.next(next[2]);
break;
}
} else if (next[1] in redirects_data) {
if (false) {
console.trace('已處理過 have registered, use cache: ' + next[1]
+ '→' + redirects_data[next[1]]);
}
// next[2] : callback(root_page_data, error)
this.next(next[2]);
break;
} else if (next[1] in this.nonexistent_pages) {
// next[2] : callback(root_page_data, error)
this.next(next[2]);
break;
}
if (Array.isArray(next[1])) {
// next[3] : options
var slice_size = next[3].one_by_one ? 1
// 50: 避免 HTTP status 414: Request-URI Too Long
: next[3].slice_size >= 1 ? Math.min(50, next[3].slice_size)
: 50;
while (next[1].length > slice_size) {
_this.actions.unshift([ next[0],
// keep request order
slice_size === 1 ? next[1].pop()
//
: next[1].splice(next[1].length - slice_size, slice_size),
next[2], next[3] ]);
// remove callback: only run callback at the latest
// time.
next[2] = undefined;
}
}
// console.trace(JSON.stringify(next[1]));
// 解析出所有 next[1] 別名
// next[1]: page_title
wiki_API.redirects_here(next[1], function(root_page_data,
redirect_list, error) {
// console.trace([ root_page_data, redirect_list, error ]);
if (error) {
// console.trace(error);
// next[2] : callback(root_page_data, error)
_this.next(next[2], null, error);
return;
}
if (false) {
console.trace(root_page_data);
console.trace(redirect_list);
console.assert(!redirect_list
|| redirect_list === root_page_data.redirect_list);
}
var page_list_to_check_variants
//
= Array.isArray(next[1]) ? next[1] : [ next[1] ];
// from: alias, to: 正式名稱
function register_title(from, to) {
if (!from) {
return;
}
// assert: from ===
// _this.normalize_title(from)
// the namespace of from, to is normalized
// from === to 亦登記,以供確認此頁面存在。
// 並方便 register_redirect_list_via_mapper() 中的 map_to 設定。
if (redirects_data[from]) {
if (redirects_data[from] === to)
return;
// Should not fo to here.
library_namespace
.error('register_title: ' + from + '→'
+ redirects_data[from]
+ '\n\tchange to →' + to);
}
redirects_data[from] = to;
page_list_to_check_variants.push(from);
}
function register_root_alias(page_data) {
if (page_data.original_title) {
// console.trace(page_data);
register_title(page_data.original_title,
//
page_data.title);
}
if (page_data.redirect_from) {
register_title(page_data.redirect_from,
//
page_data.title);
}
}
function register_redirect_list(redirect_list, page_title) {
// console.trace(redirect_list);
// 本名
var target_page_title = redirect_list[0].title;
var is_missing = !target_page_title
|| ('missing' in redirect_list[0])
|| ('invalid' in redirect_list[0]);
if (!is_missing) {
redirect_list.forEach(function(page_data) {
register_title(page_data.title, target_page_title);
});
}
if (next[3].no_message) {
return;
}
var message = 'register_redirects: '
+ (page_title === target_page_title ? ''
: (wiki_API.title_link_of(page_title)
// JSON.stringify(page_title)
// Should not go to here
|| page_title) + ' → ')
+ wiki_API.title_link_of(target_page_title) + ': ';
if (is_missing) {
// 避免重複 call。
_this.nonexistent_pages[page_title] = true;
message += 'Missing';
library_namespace.warn(message);
return;
}
message += gettext(redirect_list.length === 1
// gettext_config:{"id":"no-page-redirects-to-this-page"}
? '無頁面重定向至本頁'
// gettext_config:{"id":"total-$1-pages-redirected-to-this-page"}
: '共有%1個{{PLURAL:%1|頁面}}重定向至本頁', redirect_list.length - 1);
if (1 < redirect_list.length && redirect_list.length < 6) {
message += ': '
//
+ redirect_list.slice(1).map(function(page_data) {
// return page_data.title;
return wiki_API.title_link_of(page_data);
}).join(gettext('Comma-separator'));
}
library_namespace.info(message);
}
if (redirect_list) {
// e.g., wiki_API.redirects_here({String})
// console.trace([ next[1], root_page_data ]);
register_redirect_list(redirect_list,
//
Array.isArray(next[1]) ?
// assert: next[1].length === 1
next[1][0] : next[1]);
register_root_alias(root_page_data);
} else {
// e.g., wiki_API.redirects_here({Array})
root_page_data.forEach(function(page_data) {
// console.trace(page_data.redirect_list);
// console.trace(page_data.original_title);
register_redirect_list(page_data.redirect_list
|| [ page_data ], page_data.original_title
|| page_data.title);
register_root_alias(page_data);
});
}
// console.trace(redirects_data);
if (library_namespace.is_Object(
// 將 {Object}options.update_page_name_hash 之 value 視為 page name
// 並更新其 value 成為 redirect target。
// TODO: 另外提出成為 function
next[3].update_page_name_hash)) {
var update_page_name_hash = next[3].update_page_name_hash;
for ( var key in update_page_name_hash) {
var page_name = _this.to_namespace(
update_page_name_hash[key], next[3].namespace);
if (typeof page_name !== 'string')
continue;
page_name = redirects_data[page_name];
if (!page_name)
continue;
if (next[3].namespace
&& _this.is_namespace(
update_page_name_hash[key], 'main')) {
// need_remove_namespace
page_name = _this.remove_namespace(page_name);
}
update_page_name_hash[key] = page_name;
}
}
if (false) {
console.trace([ next, next[3].no_languagevariants,
!_this.has_languagevariants ]);
}
if (next[3].no_languagevariants || !_this.has_languagevariants
// /[\u4e00-\u9fa5]/: 匹配中文字 RegExp。
// https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
// || !/[\u4e00-\u9fa5]/.test(next[1])
) {
// next[2] : callback(root_page_data, error)
_this.next(next[2], root_page_data);
return;
}
// --------------------------------------------------
// 處理 converttitles。
// console.trace('處理繁簡轉換問題: ' + page_list_to_check_variants);
// console.trace(root_page_data);
// console.trace(JSON.stringify(redirects_data));
// variants_of_target[register_target]
// = [ 相對應的 variant list 1, 相對應的 variant list 2, ... ]
// 相對應的 variant list = [ variants 1, variants 2 ]
var variants_of_target = Object.create(null);
var redirects_variants_patterns
// redirects_variants_patterns[first char] = pattern_hash
= _this.redirects_variants_patterns
// pattern_hash = { pattern_id: pattern_data, ... }
// pattern_data = [ {RegExp}pattern, redirects_to, namespace ]
|| (_this.redirects_variants_patterns = Object.create(null));
// {{規范控製}} → {{规范控制}} → {{Authority control}}
// 為解決 "控製"也能引導到"控制" 此類模板繁體名稱採用繁簡共通字,因此就算 wiki.convert_Chinese()
// 也不能獲得繁體字,但繁體字會自動 mapping 的問題;必須自行
// wiki.register_redirects(繁體字名稱)。
function register_variants_pattern() {
// console.trace(variants_of_target);
for ( var register_target in variants_of_target) {
variants_of_target[register_target].forEach(function(
variants_list) {
if (variants_list.length === 1) {
// 沒有任何變體。
return;
}
// assert: variants_list.length === 2:
// [ 'zh-tw', 'zh-cn' ] or [ 'zh-hant', 'zh-hans' ]
if (false) {
console.trace(variants_list, variants_list[0]
.chars());
}
var char_list = variants_list.shift().chars()
// 先取第一個為基準。
.map(function(char) {
return [ char ];
});
if (variants_list.some(function(variant) {
variant = variant.chars();
// 兩個變體的長度不同。
if (char_list.length !== variant.length) {
return true;
}
variant.forEach(function(char, index) {
if (!char_list[index].includes(char)) {
char_list[index].push(char);
}
});
})) {
library_namespace.warn(
//
'register_variants_pattern: 跳過長度不同的變體 '
+ register_target + ' ← '
// + char_list.join('') + '|'
+ variants_list.join('|'));
return;
}
var pattern = char_list.map(function(chars) {
chars = add_char_variants(chars);
return chars.length === 1 ? chars : '['
+ chars.join('') + ']';
});
pattern = pattern.join('');
var pattern_hash;
var namespace = _this.namespace(register_target, {
is_page_title : true,
get_name : true
});
if (false) {
console.trace([ register_target, namespace,
char_list ]);
}
// 跳過 namespace + ':'
if (false && namespace) {
if (!char_list[namespace.length]
//
|| char_list[namespace.length].length !== 1
//
|| char_list[namespace.length][0] !== ':') {
console.error([ register_target, namespace,
char_list ]);
throw new Error(
'register_variants_pattern: '
+ '解析 namespace 出錯!');
}
}
(namespace ? char_list[namespace.length + 1]
: char_list).forEach(function(first_char) {
if (!redirects_variants_patterns[first_char]) {
if (!pattern_hash) {
pattern_hash = Object.create(null);
pattern_hash[KEY_first_char_list] = [];
}
pattern_hash[KEY_first_char_list]
.push(first_char);
redirects_variants_patterns[first_char]
// Initialization
= pattern_hash;
} else if (!pattern_hash) {
pattern_hash
// Copy
= redirects_variants_patterns[first_char];
// assert:
// pattern_hash[KEY_first_char_list].includes(first_char)
} else if (!pattern_hash !==
// merge
redirects_variants_patterns[first_char]) {
var first_char_list =
//
redirects_variants_patterns[first_char]
//
[KEY_first_char_list];
var old_first_char_list
//
= pattern_hash[KEY_first_char_list]
//
.filter(function(char) {
if (first_char_list.includes(char))
return;
first_char_list.push(char);
return true;
});
// assert:
// first_char_list.includes(first_char);
pattern_hash = Object.assign(
//
redirects_variants_patterns[first_char],
//
pattern_hash);
redirects_variants_patterns[first_char]
// recover
[KEY_first_char_list] = first_char_list;
// console.trace(old_first_char_list);
old_first_char_list.forEach(function(
first_char) {
redirects_variants_patterns[first_char]
//
= pattern_hash;
});
}
if (pattern_hash[pattern]
//
&& pattern_hash[pattern][1]
//
!== register_target) {
library_namespace.warn(
//
'register_variants_pattern: 相同變體重定向到不同頁面: '
+ pattern + ' → '
+ pattern_hash[pattern] + ', '
+ register_target);
}
pattern_hash[pattern] = [
new RegExp('^' + pattern + '$'
// , 'u'
), register_target,
_this.namespace(register_target) ];
});
});
}
}
function register_redirect_list_via_mapper(original_list,
variants_list, error) {
if (false) {
console.trace([ next[3].uselang, original_list,
variants_list ]);
}
if (!Array.isArray(variants_list)) {
library_namespace
.error('register_redirect_list_via_mapper: '
+ '無法繁簡轉換: ' + (error || '未知的錯誤'));
return;
}
// assert: variants_list.length === original_list.length
if (/* check_char_variants */false) {
original_list = original_list.slice(1);
variants_list = variants_list.slice(1);
}
variants_list.forEach(function(map_from, index) {
// if (map_from in redirects_data) return;
var map_to
//
= redirects_data[original_list[index]];
if (original_list.start_index) {
index += original_list.start_index;
}
if (false) {
library_namespace
.log('register_redirect_list_via_mapper: ['
+ variants_list.uselang + '] '
+ '[' + index + '] ' + map_from
+ ' → ' + map_to);
}
redirects_data[map_from] = map_to;
var variants_map = variants_of_target[map_to];
if (!variants_map)
variants_map = variants_of_target[map_to] = [];
if (!variants_map[index]) {
variants_map[index] = [ map_from ];
} else if (!variants_map[index].includes(map_from)) {
variants_map[index].push(map_from);
}
});
// console.trace(variants_of_target);
}
var promise = Promise.resolve();
function add_variant_of_page_list(uselang) {
// next[3] : options
var options = Object.assign(Object.clone(next[3]), {
uselang : uselang
});
promise = promise.then(function() {
// console.trace(uselang);
return new Promise(
/* executor */function(resolve, reject) {
// console.trace(uselang);
wiki_API.convert_Chinese(
//
page_list_to_check_variants,
//
function(converted_page_list, error) {
if (false) {
console.trace([
//
page_list_to_check_variants,
//
uselang, converted_page_list, error ]);
}
register_redirect_list_via_mapper(
page_list_to_check_variants,
converted_page_list, error);
// console.trace(variants_of_target, promise);
resolve();
}, options);
});
});
}
function check_big_variations() {
for ( var register_target in variants_of_target) {
variants_of_target[register_target]
//
= variants_of_target[register_target].filter(function(
variants_list) {
if (variants_list.length === 1) {
// 沒有任何變體。
return;
}
var length = variants_list[0].length;
for (var index = 1;
//
index < variants_list.length; index++) {
if (variants_list[index].length !== length) {
// 有地區化語詞。
page_list_to_check_variants
.append(variants_list);
return;
// break;
}
}
return true;
});
}
}
page_list_to_check_variants = page_list_to_check_variants
//
.filter(function(page_title) {
// 去掉全英文字母的頁面名稱。
return !/^[\w\s:\-]+$/.test(page_title);
});
// console.trace(page_list_to_check_variants);
if (/* check_char_variants */false) {
page_list_to_check_variants
//
.unshift(page_list_to_check_variants.join('')
// 這些字元不該有變體。
.replace(/[\w\s:"']/g, '').chars().unique().join(','));
}
var accumulated_length
// 完成一階段,reset 前必須設定 .start_index = 累積的長度 accumulated_length,預防
// register_redirect_list_via_mapper() 登記到先前的 index。
= page_list_to_check_variants.length;
[ 'zh-tw',
// zh-cn: e.g., "Template:軟體專題" ⭠ "Template:软件专题"
'zh-cn' ].forEach(add_variant_of_page_list);
promise.then(function() {
// reset
page_list_to_check_variants = [];
page_list_to_check_variants
//
.start_index = accumulated_length;
check_big_variations();
if (page_list_to_check_variants.length > 0) {
// e.g., "Template:独联体专题" ⭠ "Template:獨立國協專題"
// console.trace(page_list_to_chec