v1.0 du site web

This commit is contained in:
22107988t
2023-09-25 13:27:24 +02:00
parent 20cb812095
commit a94f68f22a
2787 changed files with 864804 additions and 0 deletions

412
app/node_modules/cejs/application/net/wiki/Flow.js generated vendored Normal file
View File

@@ -0,0 +1,412 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): Flow, Structured
* Discussions
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2019/10/11 拆分自 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.Flow',
require : 'data.native.' + '|application.net.wiki.'
// load MediaWiki module basic functions
+ '|application.net.wiki.namespace.'
//
+ '|application.net.wiki.query.',
// 設定不匯出的子函式。
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;
// @inner
var is_api_and_title = wiki_API.is_api_and_title, normalize_title_parameter = wiki_API.normalize_title_parameter;
// --------------------------------------------------------------------------------------------
// Flow page support. Flow 功能支援。
// [[mediawikiwiki:Extension:Flow/API]]
// https://www.mediawiki.org/w/api.php?action=help&modules=flow
// https://zh.wikipedia.org/w/api.php?action=query&prop=flowinfo&titles=Wikipedia_talk:Flow_tests
// https://zh.wikipedia.org/w/api.php?action=query&prop=info&titles=Wikipedia_talk:Flow_tests
// https://zh.wikipedia.org/w/api.php?action=flow&submodule=view-topiclist&page=Wikipedia_talk:Flow_tests&vtlformat=wikitext&utf8=1
// .roots[0]
// https://zh.wikipedia.org/w/api.php?action=flow&submodule=view-topic&page=Topic:sqs6skdav48d3xzn&vtformat=wikitext&utf8=1
// https://www.mediawiki.org/w/api.php?action=flow&submodule=view-header&page=Talk:Sandbox&vhformat=wikitext&utf8=1
// https://www.mediawiki.org/w/api.php?action=flow&submodule=view-topiclist&utf8=1&page=Talk:Sandbox
/**
* get the infomation of Flow.
*
* @param {String|Array}title
* page title 頁面標題。可為話題id/頁面標題+話題標題。<br />
* {String}title or [ {String}API_URL, {String}title or
* {Object}page_data ]
* @param {Function}callback
* 回調函數。 callback({Object}page_data)
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*/
function Flow_info(title, callback, options) {
var action = normalize_title_parameter(title, options);
if (!action) {
throw 'Flow_info: Invalid title: ' + wiki_API.title_link_of(title);
}
// [[mw:Extension:StructuredDiscussions/API#Detection]]
// 'prop=flowinfo' is deprecated. use 'action=query&prop=info'.
// The content model will be 'flow-board' if it's enabled.
action[1] = 'action=query&prop=info&' + action[1];
wiki_API.query(action, typeof callback === 'function'
//
&& function(data) {
if (library_namespace.is_debug(2)
// .show_value() @ interact.DOM, application.debug
&& library_namespace.show_value)
library_namespace.show_value(data, 'Flow_info: data');
var error = data && data.error;
// 檢查伺服器回應是否有錯誤資訊。
if (error) {
library_namespace.error('Flow_info: ['
//
+ error.code + '] ' + error.info);
/**
* e.g., Too many values supplied for parameter 'pageids': the
* limit is 50
*/
if (data.warnings
//
&& data.warnings.query && data.warnings.query['*'])
library_namespace.warn(data.warnings.query['*']);
callback(data, error);
return;
}
if (!data || !data.query || !data.query.pages) {
library_namespace.warn('Flow_info: Unknown response: ['
//
+ (typeof data === 'object'
//
&& typeof JSON !== 'undefined'
//
? JSON.stringify(data) : data) + ']');
if (library_namespace.is_debug()
// .show_value() @ interact.DOM, application.debug
&& library_namespace.show_value)
library_namespace.show_value(data);
callback(null, data);
return;
}
// TODO: data.query.normalized=[{from:'',to:''},...]
data = data.query.pages;
var pages = [];
for ( var pageid in data) {
var page = data[pageid];
pages.push(page);
}
// options.multi: 即使只取得單頁面,依舊回傳 Array。
if (!options || !options.multi)
if (pages.length <= 1) {
if (pages = pages[0])
pages.is_Flow = is_Flow(pages);
library_namespace.debug('只取得單頁面 [[' + pages.title
//
+ ']],將回傳此頁面資料,而非 Array。', 2, 'Flow_info');
} else {
library_namespace.debug('Get ' + pages.length
//
+ ' page(s)! The pages'
//
+ ' will all passed to callback as Array!'
//
, 2, 'Flow_info');
}
/**
* page 之 structure 將按照 wiki API 本身之 return<br />
* <code>
page_data = {ns,title,missing:'']}
page_data = {pageid,ns,title,flowinfo:{flow:[]}}
page_data = {pageid,ns,title,flowinfo:{flow:{enabled:''}}}
* </code>
*/
callback(pages);
}, null, options);
}
/**
* 檢測 page_data 是否為 Flow 討論頁面系統。
*
* other contentmodel: "MassMessageListContent"
*
* @param {Object}page_data
* page data got from wiki API.
*
* @returns {Boolean}是否為 Flow 討論頁面。
*/
function is_Flow(page_data) {
if ('contentmodel' in page_data) {
// used in prop=info
return page_data.contentmodel === 'flow-board';
}
var flowinfo = page_data &&
// wiki_API.is_page_data(page_data) &&
page_data.flowinfo;
if (flowinfo) {
// used in prop=flowinfo (deprecated)
// flowinfo:{flow:{enabled:''}}
return flowinfo.flow && ('enabled' in flowinfo.flow);
}
// e.g., 從 wiki_API.page 得到的 page_data
if (page_data = wiki_API.content_of.revision(page_data))
return (page_data.contentmodel || page_data.slots
&& page_data.slots.main
&& page_data.slots.main.contentmodel) === 'flow-board';
}
/** {Object}abbreviation 縮寫 */
var Flow_abbreviation = {
// https://www.mediawiki.org/w/api.php?action=help&modules=flow%2Bview-header
// 關於討論板的描述。使用 .revision
header : 'h',
// https://www.mediawiki.org/w/api.php?action=help&modules=flow%2Bview-topiclist
// 討論板話題列表。使用 .revisions
topiclist : 'tl'
};
/**
* get topics of the page.
*
* @param {String|Array}title
* page title 頁面標題。可為話題id/頁面標題+話題標題。 {String}title or [
* {String}API_URL, {String}title or {Object}page_data ]
* @param {Function}callback
* 回調函數。 callback({Object}topiclist)
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*/
function Flow_page(title, callback, options) {
// 處理 [ {String}API_URL, {String}title or {Object}page_data ]
if (!is_api_and_title(title)) {
title = [ options[KEY_SESSION] && options[KEY_SESSION].API_URL,
title ];
}
var page_data;
if (wiki_API.is_page_data(title[1]))
page_data = title[1];
title[1] = 'page=' + encodeURIComponent(wiki_API.title_of(title[1]));
if (options && options.redirects) {
// 舊版毋須 '&redirects=1''&redirects' 即可。
title[1] += '&redirects=1';
}
// e.g., { flow_view : 'header' }
var view = options && options.flow_view
//
|| Flow_page.default_flow_view;
title[1] = 'action=flow&submodule=view-' + view + '&v'
+ (Flow_abbreviation[view] || view.charAt(0).toLowerCase())
+ 'format=' + (options && options.format || 'wikitext') + '&'
+ title[1];
if (!title[0])
title = title[1];
wiki_API.query(title, typeof callback === 'function'
//
&& function(data) {
if (library_namespace.is_debug(2)
// .show_value() @ interact.DOM, application.debug
&& library_namespace.show_value)
library_namespace.show_value(data, 'Flow_page: data');
var error = data && data.error;
// 檢查伺服器回應是否有錯誤資訊。
if (error) {
library_namespace.error(
//
'Flow_page: [' + error.code + '] ' + error.info);
callback(page_data);
return;
}
// data =
// { flow: { 'view-topiclist': { result: {}, status: 'ok' } } }
if (!(data = data.flow)
//
|| !(data = data['view-' + view]) || data.status !== 'ok') {
library_namespace.error(
//
'Flow_page: Error status [' + (data && data.status) + ']');
callback(page_data);
return;
}
if (page_data)
// assert: data.result = { ((view)) : {} }
Object.assign(page_data, data.result);
else
page_data = data.result[view];
callback(page_data);
}, null, options);
}
/** {String}default view to flow page */
Flow_page.default_flow_view = 'topiclist';
/**
* Create a new topic. 發新話題。 Reply to an existing topic.
*
* @param {String|Array}title
* page title 頁面標題。 {String}title or [ {String}API_URL,
* {String}title or {Object}page_data ]
* @param {String}topic
* 新話題的標題文字。 {String}topic
* @param {String|Function}text
* page contents 頁面內容。 {String}text or {Function}text(page_data)
* @param {Object}token
* login 資訊包含“csrf”令牌/密鑰。
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
* @param {Function}[callback]
* 回調函數。 callback(title, error, result)
*
* @see https://www.mediawiki.org/w/api.php?action=help&modules=flow%2Bnew-topic
* https://www.mediawiki.org/w/api.php?action=help&modules=flow%2Breply
*/
function edit_topic(title, topic, text, token, options, callback) {
// console.log(text);
if (library_namespace.is_thenable(text)) {
text.then(function(text) {
edit_topic(title, topic, text, token, options, callback);
}, function(error) {
callback(title, error);
});
return;
}
var action = 'action=flow';
// 處理 [ {String}API_URL, {String}title or {Object}page_data ]
if (Array.isArray(title)) {
action = [ title[0], action ];
title = title[1];
} else if (options[KEY_SESSION]) {
action = [ options[KEY_SESSION].API_URL, action ];
}
if (wiki_API.is_page_data(title))
title = title.title;
// assert: typeof title === 'string' or title is invalid.
if (title.length > 260) {
// [nttopic] 話題標題已限制在 260 位元組內。
// 自動評論與摘要的長度限制是260個字符。需要小心任何超出上述限定的東西將被裁剪掉。
// 260 characters
// https://github.com/wikimedia/mediawiki-extensions-Flow/blob/master/includes/Model/PostRevision.php
// const MAX_TOPIC_LENGTH = 260;
// https://github.com/wikimedia/mediawiki-extensions-Flow/blob/master/i18n/zh-hant.json
library_namespace
.warn('edit_topic: Title is too long and will be truncated: ['
+ error.code + ']');
title = title.slice(0, 260);
}
// default parameters
var _options = {
// notification_name : 'flow',
submodule : 'new-topic',
page : title,
nttopic : topic,
ntcontent : text,
ntformat : 'wikitext'
};
edit_topic.copy_keys.forEach(function(key) {
if (options[key])
_options[key] = options[key];
});
// the token should be sent as the last parameter.
_options.token = library_namespace.is_Object(token) ? token.csrftoken
: token;
wiki_API.query(action, typeof callback === 'function'
//
&& function(data) {
if (library_namespace.is_debug(2)
// .show_value() @ interact.DOM, application.debug
&& library_namespace.show_value)
library_namespace.show_value(data, 'edit_topic: data');
var error = data && data.error;
// 檢查伺服器回應是否有錯誤資訊。
if (error) {
library_namespace.error('edit_topic: ['
//
+ error.code + '] ' + error.info);
} else if (!(data = data.flow)
//
|| !(data = data['new-topic']) || data.status !== 'ok') {
// data = { flow: { 'new-topic': { status: 'ok',
// workflow: '', committed: {} } } }
error = 'edit_topic: Bad status ['
//
+ (data && data.status) + ']';
library_namespace.error(error);
}
if (typeof callback === 'function') {
// title.title === wiki_API.title_of(title)
callback(title.title, error, data);
}
}, _options, options);
}
/** {Array}欲 copy 至 Flow edit parameters 之 keys。 */
edit_topic.copy_keys = 'summary|bot|redirect|nocreate'.split(',');
// ------------------------------------------------------------------------
// export 導出.
// CeL.wiki.Flow.*
Object.assign(Flow_info, {
is_Flow : is_Flow,
page : Flow_page,
edit : edit_topic
});
return Flow_info;
}

75
app/node_modules/cejs/application/net/wiki/README.wiki generated vendored Normal file
View File

@@ -0,0 +1,75 @@
<!-- The first line is blank due to BOM -->
= CeJS MediaWiki module =
MediaWiki 自動化作業程式庫,主要用於編寫[[維基百科:機器人]]。
== Usage in [https://www.mediawiki.org/wiki/Manual:Interface/JavaScript mediawiki user script] (User:Example/common.js) ==
<pre><code>
if (!window.CeL) {
window.CeL = { initializer: function() { CeL.run('application.net.wiki', CeL_initialization); } };
mw.loader.load('https://kanasimi.github.io/CeJS/ce.js');
}
function CeL_initialization() {
/** {Array} parsed page content */
const parsed = CeL.wiki.parser('{{tl|t}}');
parsed.each('template', function(token) { console.log(token.name); });
const wiki = CeL.wiki.mw_web_session;
// wiki.page('Wikipedia:Sandbox').edit(function(page_data) { return CeL.wiki.content_of(page_data) + '\ntest'; });
}
</code></pre>
(At 2021, The JavaScript parser of MediaWiki loader cannot read ECMAScript 2016 syntax.)
Also refer to [https://kanasimi.github.io/CeJS/_test%20suite/wikitext_parser.html the wikitext parser examples].
; Append parameter to template:
<pre><code>
const wiki = CeL.wiki.mw_web_session;
wiki.page(mw.config.get('wgPageName')).edit(function(page_data) {
/** {Array} parsed page content */
const parsed = CeL.wiki.parser(page_data).parse();
parsed.each('Template:Artwork', function(token) {
token.push('wikidata=Q27964733');
});
return parsed.toString();
}, {
summary: 'test edit'
}).run(function() {
location.reload();
});
</code></pre>
== Operation mechanism 運作機制 ==
Main initial point: [[../wiki.js]]
; Essential 必要: [[../wiki.js]] → [[namespace.js]] → [[parser.js]], [[query.js]], [[page.js]], [[Flow.js]], [[list.js]], [[edit.js]], [[task.js]]
; Optional 可選功能: [[data.js]], [[admin.js]], [[cache.js]], [[Toolforge.js]]
; Change with wikiproject page contents 隨各 wikiproject 頁面內容變化之功能: [[template_functions.js]], [[featured_content.js]]
More examples: 使用範例可參照:
<!--
const util = require('util'); new util.promisify(CeL.wiki)(...)
-->
* [https://github.com/kanasimi/wikiapi JavaScript MediaWiki API for ECMAScript 2017+] / [https://github.com/kanasimi/wikiapi/blob/master/wikiapi.js wikiapi.js]
* [https://github.com/kanasimi/wikibot Wikipedia bots demo] / [https://github.com/kanasimi/wikibot/blob/master/wiki%20loader.js wiki loader.js]
* [[/_test suite/test.js|test.js]]
* [https://kanasimi.github.io/CeJS/_test%20suite/wikitext_parser.html Wikitext parser examples. Wikitext 解析器使用例子]
== History ==
{| class="wikitable"
|+ History 沿革
! Date !! Modify
|-
| 2015/1/1 || Starting to write codes.
開始撰寫模組程式碼。
|-
| 2019/10/11 || 分拆至 wiki/*.js
|-
| 2020/5/24 || 分拆 wiki.js。基本功能僅需要 `CeL.run('application.net.wiki')`。
|}
== See also ==
* [https://www.mediawiki.org/w/api.php MediaWiki API help]

902
app/node_modules/cejs/application/net/wiki/Toolforge.js generated vendored Normal file
View File

@@ -0,0 +1,902 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): Toolforge only functions
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* 條件合適時,應該會由 CeL.application.net.wiki 載入。
*
* TODO:<code>
</code>
*
* @since 2019/10/11 拆分自 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.Toolforge',
require : 'data.native.|application.storage.' + '|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;
// ------------------------------------------------------------------------
// SQL 相關函數 @ Toolforge。
var
/** {String}user home directory */
home_directory = library_namespace.env.home,
/** {String}Wikimedia Toolforge database host */
TOOLSDB = 'tools-db',
/** {String}user/bot name */
user_name,
/** {String}Wikimedia Toolforge name. CeL.wiki.wmflabs */
wmflabs = wiki_API.wmflabs,
/** {Object}Wikimedia Toolforge job data. CeL.wiki.job_data */
job_data,
/** node mysql handler */
node_mysql,
/** {Object}default SQL configurations */
SQL_config;
if (home_directory
&& (home_directory = home_directory.replace(/[\\\/]$/, '').trim())) {
user_name = home_directory.match(/[^\\\/]+$/);
user_name = user_name ? user_name[0] : undefined;
if (user_name) {
wiki_API.user_name = user_name;
}
// There is no CeL.storage.append_path_separator() here!
home_directory += library_namespace.env.path_separator;
}
// setup SQL config language (and database/host).
function set_SQL_config_language(language) {
if (!language) {
return;
}
if (typeof language !== 'string') {
library_namespace.error(
//
'set_SQL_config_language: Invalid language: [' + language + ']');
return;
}
if (language === TOOLSDB) {
this.host = language;
// delete this.database;
return;
}
// 正規化。
var site = wiki_API.site_name(language);
// TODO: 'zh.news'
// 警告: this.language 可能包含 'zhwikinews' 之類。
this.host = site + set_SQL_config_language.hostname_postfix;
/**
* The database names themselves consist of the mediawiki project name,
* suffixed with _p
*
* @see https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database
*/
this.database = site + '_p';
// console.log(this);
}
// https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database#Connecting_to_the_database_replicas
// .analytics.db.svc.wikimedia.cloud
// @seealso https://phabricator.wikimedia.org/T142807
set_SQL_config_language.hostname_postfix = '.web.db.svc.wikimedia.cloud';
/**
* return new SQL config
*
* @param {String}[language]
* database language.<br />
* e.g., 'en', 'commons', 'wikidata', 'meta'.
* @param {String}[user]
* SQL database user name
* @param {String}[password]
* SQL database user password
*
* @returns {Object}SQL config
*/
function new_SQL_config(language, user, password) {
var config, is_clone;
if (user) {
config = {
user : user,
password : password,
db_prefix : user + '__',
set_language : set_SQL_config_language
};
} else if (SQL_config) {
is_clone = true;
config = Object.clone(SQL_config);
} else {
config = {};
}
if (typeof language === 'object') {
if (is_clone) {
delete config.database;
}
if (language.API_URL) {
// treat language as session.
// use set_SQL_config_language()
config.set_language(wiki_API.site_name(language), !user);
} else {
Object.assign(config, language);
}
} else if (typeof language === 'string' && language) {
if (is_clone) {
delete config.database;
}
// change language (and database/host).
config.set_language(language, !user);
}
return config;
}
/**
* 讀取並解析出 SQL 設定。
*
* @param {String}file_name
* file name
*
* @returns {Object}SQL config
*/
function parse_SQL_config(file_name) {
var config;
try {
config = library_namespace.get_file(file_name);
} catch (e) {
library_namespace.error(
//
'parse_SQL_config: Cannot read config file [ ' + file_name + ']!');
return;
}
// 應該用 parser。
var user = config.match(/\n\s*user\s*=\s*(\S+)/), password;
if (!user || !(password = config.match(/\n\s*password\s*=\s*(\S+)/)))
return;
return new_SQL_config(wiki_API.language, user[1], password[1]);
}
if (wmflabs) {
try {
node_mysql = require('mysql');
if (node_mysql) {
SQL_config = parse_SQL_config(home_directory
// The production replicas.
// https://wikitech.wikimedia.org/wiki/Help:Toolforge#The_databases
// https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database
// Wikimedia Toolforge
// 上之資料庫僅為正式上線版之刪節副本。資料並非最新版本(但誤差多於數分內),也不完全,
// <del>甚至可能為其他 users 竄改過</del>。
+ 'replica.my.cnf');
}
} catch (e) {
library_namespace.error(e);
}
if (process.env.JOB_ID && process.env.JOB_NAME) {
// assert: process.env.ENVIRONMENT === 'BATCH'
wiki_API.job_data = job_data = {
id : process.env.JOB_ID,
name : process.env.JOB_NAME,
request : process.env.REQUEST,
script : process.env.JOB_SCRIPT,
stdout_file : process.env.SGE_STDOUT_PATH,
stderr_file : process.env.SGE_STDERR_PATH,
// 'continuous' or 'task'
is_task : process.env.QUEUE === 'task'
};
}
}
// ------------------------------------------------------------------------
/**
* execute SQL command.
*
* @param {String}SQL
* SQL command.
* @param {Function}callback
* 回調函數。 callback({Object}error, {Array}rows, {Array}fields)
* @param {Object}[config]
* configuration.
*
* @see https://wikitech.wikimedia.org/wiki/Help:Toolforge/Database
*
* @require https://github.com/mysqljs/mysql <br />
* https://quarry.wmflabs.org/ <br />
* TODO: https://github.com/sidorares/node-mysql2
*/
function run_SQL(SQL, callback, config) {
var _callback = function(error, results, fields) {
// the connection will return to the pool, ready to be used again by
// someone else.
// connection.release();
// close the connection and remove it from the pool
// connection.destroy();
callback(error, results, fields);
};
_callback = callback;
// TypeError: Converting circular structure to JSON
// library_namespace.debug(JSON.stringify(config), 3, 'run_SQL');
if (!config && !(config = SQL_config)) {
return;
}
// treat config as language.
if (typeof config === 'string' || wiki_API.is_wiki_API(config)) {
config = new_SQL_config(config);
}
library_namespace.debug(String(SQL), 3, 'run_SQL');
// console.log(JSON.stringify(config));
var connection = node_mysql.createConnection(config);
connection.connect();
if (Array.isArray(SQL)) {
// ("SQL", [values], callback)
connection.query(SQL[0], SQL[1], _callback);
} else {
// ("SQL", callback)
connection.query(SQL, _callback);
}
connection.end();
}
if (false) {
CeL.wiki.SQL('SELECT * FROM `revision` LIMIT 3000,1;',
//
function(error, rows, fields) {
if (error)
throw error;
// console.log('The result is:');
console.log(rows);
});
}
// ------------------------------------------------------------------------
/**
* Create a new user database.
*
* @param {String}dbname
* database name.
* @param {Function}callback
* 回調函數。
* @param {String}[language]
* database language.<br />
* e.g., 'en', 'commons', 'wikidata', 'meta'.
*
* @see https://wikitech.wikimedia.org/wiki/Help:Tool_Labs/Database#Creating_new_databases
*/
function create_database(dbname, callback, language) {
if (!SQL_config)
return;
var config;
if (typeof dbname === 'object') {
config = Object.clone(dbname);
dbname = config.database;
delete config.database;
} else {
config = new_SQL_config(language || TOOLSDB);
if (!language) {
delete config.database;
}
}
library_namespace.log('create_database: Try to create database ['
+ dbname + ']');
if (false) {
/**
* 用此方法會:<br />
* [Error: ER_PARSE_ERROR: You have an error in your SQL syntax;
* check the manual that corresponds to your MariaDB server version
* for the right syntax to use near ''user__db'' at line 1]
*/
var SQL = {
// placeholder 佔位符
// 避免 error.code === 'ER_DB_CREATE_EXISTS'
sql : 'CREATE DATABASE IF NOT EXISTS ?',
values : [ dbname ]
};
}
if (dbname.includes('`'))
throw new Error('Invalid database name: [' + dbname + ']');
run_SQL('CREATE DATABASE IF NOT EXISTS `' + dbname + '`', function(
error, rows, fields) {
if (typeof callback !== 'function')
return;
if (error)
callback(error);
else
callback(null, rows, fields);
}, config);
return config;
}
// ------------------------------------------------------------------------
/**
* SQL 查詢功能之前端。
*
* @example <code>
// change language (and database/host).
//CeL.wiki.SQL.config.set_language('en');
CeL.wiki.SQL(SQL, function callback(error, rows, fields) { if(error) console.error(error); else console.log(rows); }, 'en');
// get sitelink count of wikidata items
// https://www.mediawiki.org/wiki/Wikibase/Schema/wb_items_per_site
// https://www.wikidata.org/w/api.php?action=help&modules=wbsetsitelink
var SQL_get_sitelink_count = 'SELECT ips_item_id, COUNT(*) AS `link_count` FROM wb_items_per_site GROUP BY ips_item_id LIMIT 10';
var SQL_session = new CeL.wiki.SQL(function(error){}, 'wikidata');
function callback(error, rows, fields) { if(error) console.error(error); else console.log(rows); SQL_session.connection.destroy(); }
SQL_session.SQL(SQL_get_sitelink_count, callback);
// one-time method
CeL.wiki.SQL(SQL_get_sitelink_count, callback, 'wikidata');
* </code>
*
* @example <code>
// 進入 default host (TOOLSDB)。
var SQL_session = new CeL.wiki.SQL(()=>{});
// 進入 default host (TOOLSDB),並預先創建 user's database 'dbname' (e.g., 's00000__dbname')
var SQL_session = new CeL.wiki.SQL('dbname', ()=>{});
// 進入 zhwiki.zhwiki_p。
var SQL_session = new CeL.wiki.SQL(()=>{}, 'zh');
// 進入 zhwiki.zhwiki_p並預先創建 user's database 'dbname' (e.g., 's00000__dbname')
var SQL_session = new CeL.wiki.SQL('dbname', ()=>{}, 'zh');
// create {SQL_session}instance
new CeL.wiki.SQL('mydb', function callback(error, rows, fields) { if(error) console.error(error); } )
// run SQL query
.SQL(SQL, function callback(error, rows, fields) { if(error) console.error(error); } );
SQL_session.connection.destroy();
* </code>
*
* @param {String}[dbname]
* database name.
* @param {Function}callback
* 回調函數。 callback(error)
* @param {String}[language]
* database language (and database/host). default host: TOOLSDB.<br />
* e.g., 'en', 'commons', 'wikidata', 'meta'.
*
* @returns {SQL_session}instance
*
* @constructor
*/
function SQL_session(dbname, callback, language) {
if (!(this instanceof SQL_session)) {
if (typeof language === 'object') {
language = new_SQL_config(language);
} else if (typeof language === 'string' && language) {
// change language (and database/host).
SQL_config.set_language(language);
if (language === TOOLSDB)
delete SQL_config.database;
language = null;
}
// dbname as SQL query string.
return run_SQL(dbname, callback, language);
}
if (typeof dbname === 'function' && !language) {
// shift arguments
language = callback;
callback = dbname;
dbname = null;
}
this.config = new_SQL_config(language || TOOLSDB);
if (dbname) {
if (typeof dbname === 'object') {
Object.assign(this.config, dbname);
} else {
// 自動添加 prefix。
this.config.database = this.config.db_prefix + dbname;
}
} else if (this.config.host === TOOLSDB) {
delete this.config.database;
} else {
// this.config.database 已經在 set_SQL_config_language() 設定。
}
var _this = this;
this.connect(function(error) {
// console.error(error);
if (error && error.code === 'ER_BAD_DB_ERROR'
&& !_this.config.no_create && _this.config.database) {
// Error: ER_BAD_DB_ERROR: Unknown database '...'
create_database(_this.config, callback);
} else if (typeof callback === 'function') {
callback(error);
}
});
}
// need reset connection,
function need_reconnect(error) {
return error
// Error: Cannot enqueue Handshake after fatal error.
&& (error.code === 'PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR'
// ECONNRESET: socket hang up
|| error.code === 'ECONNRESET');
}
// run SQL query
SQL_session.prototype.SQL = function(SQL, callback) {
var _this = this;
this.connection.query(SQL, function(error) {
if (need_reconnect(error)) {
// re-connect. 可能已經斷線。
_this.connection.connect(function(error) {
if (error) {
// console.error(error);
}
_this.connection.query(SQL, callback);
});
} else {
callback.apply(null, arguments);
}
});
return this;
};
SQL_session.prototype.connect = function(callback, force) {
if (!force)
try {
var _this = this;
this.connection.connect(function(error) {
if (need_reconnect(error)) {
// re-connect.
_this.connect(callback, true);
} else if (typeof callback === 'function')
callback(error);
});
return this;
} catch (e) {
// TODO: handle exception
}
try {
this.connection.end();
} catch (e) {
// TODO: handle exception
}
// 需要重新設定 this.connection否則會出現:
// Error: Cannot enqueue Handshake after invoking quit.
this.connection = node_mysql.createConnection(this.config);
this.connection.connect(callback);
return this;
};
/**
* get database list.
*
* <code>
var SQL_session = new CeL.wiki.SQL('testdb',
//
function callback(error, rows, fields) {
if (error)
console.error(error);
else
s.databases(function(list) {
console.log(list);
});
});
</code>
*
* @param {Function}callback
* 回調函數。
* @param {Boolean}all
* get all databases. else: get my databases.
*
* @returns {SQL_session}
*/
SQL_session.prototype.databases = function(callback, all) {
var _this = this;
function filter(dbname) {
return dbname.startsWith(_this.config.db_prefix);
}
if (this.database_cache) {
var list = this.database_cache;
if (!all)
// .filter() 會失去 array 之其他屬性。
list = list.filter(filter);
if (typeof callback === 'function')
callback(list);
return this;
}
var SQL = 'SHOW DATABASES';
if (false && !all)
// SHOW DATABASES LIKE 'pattern';
SQL += " LIKE '" + this.config.db_prefix + "%'";
this.connect(function(error) {
// reset connection,
// 預防 PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR
_this.connection.query(SQL, function(error, rows, fields) {
if (error || !Array.isArray(rows)) {
library_namespace.error(error);
rows = null;
} else {
rows = rows.map(function(row) {
for ( var field in row)
return row[field];
});
_this.database_cache = rows;
if (!all)
// .filter() 會失去 array 之其他屬性。
rows = rows.filter(filter);
// console.log(rows);
}
if (typeof callback === 'function')
callback(rows);
});
});
return this;
};
if (SQL_config) {
library_namespace
.debug('wiki_API.SQL_session: You may use SQL to get data.');
wiki_API.SQL = SQL_session;
// export 導出: CeL.wiki.SQL() 僅可在 Wikimedia Toolforge 上使用。
wiki_API.SQL.config = SQL_config;
// wiki_API.SQL.create = create_database;
}
// ----------------------------------------------------
/**
* Convert MediaWiki database timestamp to ISO 8601 format.<br />
* UTC: 'yyyymmddhhmmss' → 'yyyy-mm-ddThh:mm:ss'
*
* @param {String|Buffer}timestamp
* MediaWiki database timestamp
*
* @returns {String}ISO 8601 Data elements and interchange formats
*
* @see https://www.mediawiki.org/wiki/Manual:Timestamp
*/
function SQL_timestamp_to_ISO(timestamp) {
if (!timestamp) {
// ''?
return;
}
// timestamp可能為{Buffer}
timestamp = timestamp.toString('utf8').chunk(2);
if (timestamp.length !== 7) {
// 'NULL'?
return;
}
return timestamp[0] + timestamp[1]
//
+ '-' + timestamp[2] + '-' + timestamp[3]
//
+ 'T' + timestamp[4] + ':' + timestamp[5] + ':' + timestamp[6] + 'Z';
}
function generate_SQL_WHERE(condition, field_prefix) {
var condition_array = [], value_array = [];
if (typeof condition === 'string') {
;
} else if (Array.isArray(condition)) {
// TODO: for ' OR '
condition = condition.join(' AND ');
} else if (library_namespace.is_Object(condition)) {
for ( var name in condition) {
var value = condition[name];
if (value === undefined) {
// 跳過這一筆設定。
continue;
}
if (!name) {
// condition[''] = [ condition 1, condition 2, ...];
if (Array.isArray(value)) {
value_array.append(value);
} else {
value_array.push(value);
}
continue;
}
if (!/^[a-z_]+$/.test(name)) {
throw 'Invalid field name: ' + name;
}
if (!name.startsWith(field_prefix)) {
name = field_prefix + name;
}
var matched = typeof value === 'string'
// TODO: for other operators
// @see https://mariadb.com/kb/en/mariadb/select/
// https://mariadb.com/kb/en/mariadb/functions-and-operators/
&& value.match(/^([<>!]?=|[<>]|<=>|IN |IS )([\s\S]+)$/);
if (matched) {
name += matched[1] + '?';
// DO NOT quote the value yourself!!
value = matched[2];
// Number.MAX_SAFE_INTEGER starts from 9.
if (/^[+\-]?[1-9]\d{0,15}$/.test(value)
// ↑ 15 = String(Number.MAX_SAFE_INTEGER).length-1
&& +value <= Number.MAX_SAFE_INTEGER) {
value = +value;
}
} else {
name += '=?';
}
condition_array.push(name);
value_array.push(value);
}
// TODO: for ' OR '
condition = condition_array.join(' AND ');
} else {
library_namespace.error('Invalid condition: '
+ JSON.stringify(condition));
return;
}
return [ condition ? ' WHERE ' + condition : '', value_array ];
}
// ----------------------------------------------------
// https://www.mediawiki.org/wiki/API:RecentChanges
// const
var ENUM_rc_type = 'edit,new,move,log,move over redirect,external,categorize';
/**
* Get page title 頁面標題 list of [[Special:RecentChanges]] 最近更改.
*
* @examples<code>
// get title list
CeL.wiki.recent(function(rows){console.log(rows.map(function(row){return row.title;}));}, {language:'ja', namespace:0, limit:20});
// 應並用 timestamp + this_oldid
CeL.wiki.recent(function(rows){console.log(rows.map(function(row){return [row.title,row.rev_id,row.row.rc_timestamp.toString()];}));}, {where:{timestamp:'>=20170327143435',this_oldid:'>'+43772537}});
</code>
*
* TODO: filter
*
* @param {Function}callback
* 回調函數。 callback({Array}page title 頁面標題 list)
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項。
*
* @see https://www.mediawiki.org/wiki/Manual:Recentchanges_table
* https://www.mediawiki.org/wiki/Actor_migration
*/
function get_recent_via_databases(callback, options) {
if (options && (typeof options === 'string')) {
options = {
// treat options as language
language : options
};
} else {
options = library_namespace.setup_options(options);
}
// console.trace(options);
var SQL = options.SQL;
if (!SQL) {
SQL = Object.create(null);
if (options.bot === 0 || options.bot === 1) {
// assert: 0 || 1
SQL.bot = options.bot;
}
// 不指定namespace或者指定namespace為((undefined)): 取得所有的namespace。
/** {Integer|String}namespace NO. */
var namespace = wiki_API.namespace(options.namespace);
if (namespace !== undefined) {
SQL.namespace = namespace;
}
Object.assign(SQL,
// {String|Array|Object}options.where: 自訂篩選條件。
options.where);
SQL = generate_SQL_WHERE(SQL, 'rc_');
// console.log(SQL);
// https://phabricator.wikimedia.org/T223406
// TODO: 舊版上 `actor`, `comment` 這兩個資料表不存在會出錯,需要先偵測。
// TODO: use JSON: https://phabricator.wikimedia.org/T299417
var fields = [
'*',
// https://www.mediawiki.org/wiki/Manual:Actor_table#actor_id
'(SELECT `actor_user` FROM `actor` WHERE `actor`.`actor_id` = `recentchanges`.`rc_actor`) AS `userid`',
'(SELECT `actor_name` FROM `actor` WHERE `actor`.`actor_id` = `recentchanges`.`rc_actor`) AS `user_name`',
// https://www.mediawiki.org/wiki/Manual:Comment_table#comment_id
'(SELECT `comment_text` FROM `comment` WHERE `comment`.`comment_id` = `recentchanges`.`rc_comment_id`) AS `comment`',
'(SELECT `comment_data` FROM `comment` WHERE `comment`.`comment_id` = `recentchanges`.`rc_comment_id`) AS `comment_data`' ];
SQL[0] = 'SELECT ' + fields.join(',')
// https://www.mediawiki.org/wiki/Manual:Recentchanges_table
+ ' FROM `recentchanges`' + SQL[0]
// new → old, may contain duplicate title.
// or `rc_timestamp`
// or rc_this_oldid, but too slow (no index).
// ASC: 小 → 大DESC: 大 → 小
+ ' ORDER BY `rc_this_oldid` ASC LIMIT ' + (
/** {ℕ⁰:Natural+0}limit count. */
options.limit > 0 ? Math.min(options.limit
// 筆數限制。就算隨意輸入,強制最多只能這麼多筆資料。
, 1e4)
// default records to get
: options.where ? 1e4 : 5000);
}
if (false) {
console.log([ options.config, options.language,
options[KEY_SESSION] && options[KEY_SESSION].language ]);
console.log(options[KEY_SESSION]);
throw 1;
}
run_SQL(SQL, function(error, rows, fields) {
if (error) {
callback();
return;
}
var result = [];
rows.forEach(function(row) {
if (!(row.rc_user > 0) && !(row.rc_type < 5)
//
&& (!('rc_type' in options)
//
|| options.rc_type !== ENUM_rc_type[row.rc_type])) {
// On wikis using Wikibase the results will otherwise be
// meaningless.
return;
}
var namespace_text = row.rc_namespace
// pass session = options[KEY_SESSION]
? wiki_API.namespace.name_of(row.rc_namespace, options) + ':'
: '';
// 基本上 API 盡可能模擬 recentchanges與之一致。
result.push({
type : ENUM_rc_type[row.rc_type],
// namespace
ns : row.rc_namespace,
// .rc_title 未加上 namespace prefix!
title : (namespace_text
// @see normalize_page_name()
+ row.rc_title.toString()).replace(/_/g, ' '),
// links to the page_id key in the page table
// 0: 可能為flow. 此時title為主頁面名非topic。由.rc_params可獲得相關資訊。
pageid : row.rc_cur_id,
// rev_id
// Links to the rev_id key of the new page revision
// (after the edit occurs) in the revision table.
revid : row.rc_this_oldid,
old_revid : row.rc_last_oldid,
rcid : row.rc_id,
user : row.user_name && row.user_name.toString()
// text of the username for the user that made the
// change, or the IP address if the change was made by
// an unregistered user. Corresponds to rev_user_text
//
// `rc_user_text` deprecated: MediaWiki version: ≤ 1.33
|| row.rc_user_text && row.rc_user_text.toString(),
// NULL for anonymous edits
userid : row.userid
// 0 for anonymous edits
// `rc_user` deprecated: MediaWiki version: ≤ 1.33
|| row.rc_user,
// old_length
oldlen : row.rc_old_len,
// new length
newlen : row.rc_new_len,
// Corresponds to rev_timestamp
// use new Date(.timestamp)
timestamp : SQL_timestamp_to_ISO(row.rc_timestamp),
comment : row.comment && row.comment.toString()
// `rc_comment` deprecated: MediaWiki version: ≤ 1.32
|| row.rc_comment && row.rc_comment.toString(),
// usually NULL
comment_data : row.comment_data
&& row.comment_data.toString(),
// parsedcomment : TODO,
logid : row.rc_logid,
// TODO
logtype : row.rc_log_type,
logaction : row.rc_log_action.toString(),
// logparams: TODO: should be {Object}, e.g., {userid:0}
logparams : row.rc_params.toString(),
// tags: ["TODO"],
// 以下為recentchanges之外本函數額外加入。
is_new : !!row.rc_new,
// e.g., 1 or 0
// is_bot : !!row.rc_bot,
// is_minor : !!row.rc_minor,
// e.g., mw.edit
is_Flow : row.rc_source.toString() === 'flow',
// patrolled : !!row.rc_patrolled,
// deleted : !!row.rc_deleted,
row : row
});
});
callback(result);
},
// SQL config
options.config || options.language || options[KEY_SESSION]);
}
// 可能會因環境而不同的功能。讓 wiki_API.recent 採用較有效率的實現方式。
if (SQL_config) {
wiki_API.recent =
// SQL_config ? get_recent_via_databases : get_recent_via_API;
get_recent_via_databases;
}
// ------------------------------------------------------------------------
// export 導出.
// @inner
library_namespace.set_method(wiki_API, {
SQL_config : SQL_config,
new_SQL_config : new_SQL_config,
run_SQL : run_SQL
});
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}

372
app/node_modules/cejs/application/net/wiki/admin.js generated vendored Normal file
View File

@@ -0,0 +1,372 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): 管理員相關的 adminship
* functions
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2019/10/12 拆分自 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.admin',
require : 'application.net.wiki.'
// load MediaWiki module basic functions
+ '|application.net.wiki.namespace.'
//
+ '|application.net.wiki.query.|application.net.wiki.page.',
// 設定不匯出的子函式。
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;
// ------------------------------------------------------------------------
// administrator functions. 管理員相關函數。
// 自 options 汲取出 parameters。
// TODO: 整合進 normalize_parameters。
// default_parameters[parameter name] = required
function draw_parameters(options, default_parameters, token_type) {
if (!options) {
// Invalid options/parameters
return 'No options specified';
}
// 汲取出 parameters。
var parameters = Object.create(null);
if (default_parameters) {
for ( var parameter_name in default_parameters) {
if (parameter_name in options) {
// in case options[parameter_name] === false
if (options[parameter_name])
parameters[parameter_name] = options[parameter_name];
} else if (default_parameters[parameter_name]) {
// 表示此屬性為必須存在/指定的屬性。
// This parameter is required.
return 'No property ' + parameter_name + ' specified';
}
}
}
var session = options[KEY_SESSION];
// assert: 有parameters, e.g., {Object}parameters
// 可能沒有 session
// ----------------------------
// 處理 target page。
var default_KEY_ID = 'pageid', default_KEY_TITLE = 'title', KEY_ID = default_KEY_ID, KEY_TITLE = default_KEY_TITLE;
if (parameters.to) {
// move_to
KEY_ID = 'fromid';
KEY_TITLE = 'from';
}
// 都先從 options 取值,再從 session 取值。
if (options[KEY_ID] >= 0 || options[default_KEY_ID] >= 0) {
parameters[KEY_ID] = options[KEY_ID] >= 0 ? options[KEY_ID]
: options[default_KEY_ID];
} else if (options[KEY_TITLE] || options[default_KEY_TITLE]) {
parameters[KEY_TITLE] = wiki_API.title_of(options[KEY_TITLE]
// options.from_title
|| options[default_KEY_TITLE]);
} else if (wiki_API.is_page_data(session && session.last_page)) {
// options.page_data
if (session.last_page.pageid >= 0)
parameters[KEY_ID] = session.last_page.pageid;
else
parameters[KEY_TITLE] = session.last_page.title;
} else {
// 可能沒有 page_data
if (library_namespace.is_debug()) {
library_namespace.error('draw_parameters: No page specified: '
+ JSON.stringify(options));
}
return 'No page id/title specified';
}
// ----------------------------
// 處理 token。
if (!token_type) {
token_type = 'csrf';
}
var token = options.token || session && session.token;
if (token && typeof token === 'object') {
// session.token.csrftoken
token = token[token_type + 'token'];
}
if (!token) {
// TODO: use session
if (false) {
library_namespace.error('draw_parameters: No token specified: '
+ options);
}
return 'No ' + token_type + 'token specified';
}
parameters.token = token;
return parameters;
}
// use "csrf" token retrieved from action=query&meta=tokens
// callback(response, error);
function wiki_operator(action, default_parameters, options, callback) {
// default_parameters
// Warning: 除 pageid/title/token 之外,這邊只要是能指定給 API 的,皆必須列入!
var parameters = draw_parameters(options, default_parameters);
// console.log(parameters);
if (!library_namespace.is_Object(parameters)) {
// error occurred.
if (typeof callback === 'function')
callback(undefined, parameters);
return;
}
// TODO: 若是頁面不存在/已刪除,那就直接跳出。
if (action === 'move') {
library_namespace.is_debug((parameters.fromid || parameters.from)
// .move_to_title
+ ' → ' + parameters.to, 1, 'wiki_operator.move');
}
wiki_API.query({
action : action
}, function(response, error) {
// console.log(JSON.stringify(response));
if (wiki_API.query.handle_error(response, error, callback)) {
return;
}
callback(response[action]);
}, parameters, options);
}
// ================================================================================================================
// wiki_API.delete(): remove / delete a page.
wiki_API['delete'] = function(options, callback) {
// https://www.mediawiki.org/w/api.php?action=help&modules=delete
/**
* response: <code>
{"delete":{"title":"Title","reason":"content was: \"...\", and the only contributor was \"[[Special:Contributions/Cewbot|Cewbot]]\" ([[User talk:Cewbot|talk]])","logid":0000}}
{"error":{"code":"nosuchpageid","info":"There is no page with ID 0.","*":"See https://test.wikipedia.org/w/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at &lt;https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce&gt; for notice of API deprecations and breaking changes."},"servedby":"mw1232"}
* </code>
*/
wiki_operator('delete', {
reason : false,
tags : false,
watchlist : false,
watchlistexpiry : false,
oldimage : false
}, options, callback);
};
// ----------------------------------------------------
// wiki_API.move_to(): move a page from `from` to target `to`.
wiki_API.move_to = function(options, callback) {
// https://www.mediawiki.org/w/api.php?action=help&modules=move
var default_parameters = {
to : true,
reason : false,
movetalk : false,
movesubpages : false,
noredirect : false,
watchlist : false,
ignorewarnings : false,
tags : false
};
if (!options || !options.reason) {
library_namespace
.warn('wiki_API.move_to: Should set reason when moving page!');
}
/**
* response: <code>
{"error":{"code":"nosuchpageid","info":"There is no page with ID 0.","*":"See https://zh.wikipedia.org/w/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at &lt;https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce&gt; for notice of API deprecations and breaking changes."},"servedby":"mw1277"}
error:
{"code":"articleexists","info":"A page of that name already exists, or the name you have chosen is not valid. Please choose another name.","*":"See https://zh.wikipedia.org/w/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at &lt;https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce&gt; for notice of API deprecations and breaking changes."}
{"code":"selfmove","info":"The title is the same; cannot move a page over itself.","*":"See https://zh.wikipedia.org/w/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at &lt;https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce&gt; for notice of API deprecations and breaking changes."}
{ from: 'from', to: 'to', reason: '', redirectcreated: '', moveoverredirect: '' }
{ error: { code: 'articleexists', info: 'A page already exists at [[:To]], or the page name you have chosen is not valid. Please choose another name.', '*': 'See https://test.wikipedia.org/w/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at &lt;https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce&gt; for notice of API deprecations and breaking changes.' } }
</code>
*/
// console.log(options);
wiki_operator('move', default_parameters, options, callback);
};
// ----------------------------------------------------
// @see wiki_API.is_protected
// Change the protection level of a page.
wiki_API.protect = function(options, callback) {
// https://www.mediawiki.org/w/api.php?action=help&modules=protect
/**
* response: <code>
{"protect":{"title":"title","reason":"存檔保護作業","protections":[{"edit":"sysop","expiry":"infinite"},{"move":"sysop","expiry":"infinite"}]}}
{"servedby":"mw1203","error":{"code":"nosuchpageid","info":"There is no page with ID 2006","*":"See https://zh.wikinews.org/w/api.php for API usage"}}
* </code>
*/
wiki_operator('protect', {
// protections: e.g., 'edit=sysop|move=sysop', 一般說來edit應與move同步。
protections : true,
// 在正式場合,最好給個好的理由。
// reason: @see [[MediaWiki:Protect-dropdown]]
reason : false,
// expiry : 'infinite',
expiry : false,
tags : false,
cascade : false,
watchlist : false
}, Object.assign({
protections : 'edit=sysop|move=sysop'
}, options), callback);
};
// ----------------------------------------------------
// rollback 僅能快速撤銷/回退/還原某一頁面最新版本之作者(最後一位使用者)一系列所有編輯至另一作者的編輯
// The rollback revision will be marked as minor.
wiki_API.rollback = function(options, callback) {
var session = wiki_API.session_of_options(options);
if (session && !session.token.rollbacktoken) {
session.get_token(function() {
wiki_API.rollback(options, callback);
}, 'rollback');
}
var parameters = draw_parameters(options, {
// default_parameters
// Warning: 除外pageid/title/token這邊只要是能指定給 API 的,皆必須列入!
user : false,
summary : false,
markbot : false,
tags : false
}, 'rollback');
if (!library_namespace.is_Object(parameters)) {
// error occurred.
if (typeof callback === 'function')
callback(undefined, parameters);
return;
}
// 都先從 options 取值,再從 session 取值。
var page_data =
// options.page_data ||
options.pageid && options || session && session.last_page;
// assert: 有parameters, e.g., {Object}parameters
// 可能沒有 session, page_data
if (!parameters.user && wiki_API.content_of.revision(page_data)) {
// 將最後一個版本的編輯者當作回退對象。
parameters.user = wiki_API.content_of.revision(page_data).user;
}
// https://www.mediawiki.org/w/api.php?action=help&modules=rollback
// If the last user who edited the page made multiple edits in a row,
// they will all be rolled back.
if (!parameters.user) {
// 抓最後的編輯者試試。
// 要用pageid的話得採page_data就必須保證兩者相同。
if (!parameters.title && page_data
&& parameters.pageid !== page_data.pageid) {
callback(undefined, 'parameters.pageid !== page_data.pageid');
return;
}
wiki_API.page(page_data || parameters.title, function(page_data,
error) {
if (error || !wiki_API.content_of.revision(page_data)
// 保證不會再持續執行。
|| !wiki_API.content_of.revision(page_data).user) {
if (false) {
library_namespace.error(
//
'wiki_API.rollback: No user name specified!');
}
callback(undefined,
//
'No user name specified and I cannot guess it!');
return;
}
wiki_API.rollback(options, callback);
}, Object.assign({
rvprop : 'ids|timestamp|user'
}, options));
return;
}
if (!('markbot' in parameters) && options.bot) {
parameters.markbot = options.bot;
}
/**
* response: <code>
{"rollback":{"title":"title","pageid":1,"summary":"","revid":9,"old_revid":7,"last_revid":1,"messageHtml":"<p></p>"}}
{"servedby":"mw1190","error":{"code":"badtoken","info":"Invalid token","*":"See https://zh.wikinews.org/w/api.php for API usage"}}
* </code>
*/
wiki_API.query({
action : 'rollback'
}, function(response) {
var error = response && response.error;
if (error) {
callback(response, error);
} else {
// revid 回滾的版本ID。
// old_revid 被回滾的第一個最新修訂的修訂ID。
// last_revid 被回滾最後一個最舊版本的修訂ID。
// 如果回滾不會改變的頁面沒有新修訂而成。在這種情況下revid將等於old_revid。
callback(response.rollback);
}
}, parameters, options);
};
// ----------------------------------------------------
// 目前的修訂,不可隱藏。
// This is the current revision. It cannot be hidden.
wiki_API.hide = function(options, callback) {
TODO;
};
// ------------------------------------------------------------------------
// export 導出.
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}

812
app/node_modules/cejs/application/net/wiki/cache.js generated vendored Normal file
View File

@@ -0,0 +1,812 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): cache
*
* @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.cache',
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 : '*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki, KEY_SESSION = wiki_API.KEY_SESSION;
// --------------------------------------------------------------------------------------------
/** {Object|Function}fs in node.js */
var node_fs;
try {
if (library_namespace.platform.nodejs)
// @see https://nodejs.org/api/fs.html
node_fs = require('fs');
if (typeof node_fs.readFile !== 'function')
throw true;
} catch (e) {
// enumerate for wiki_API.cache
// 模擬 node.js 之 fs以達成最起碼的效果即無 cache 功能的情況)。
library_namespace.warn(this.id
+ ': 無 node.js 之 fs因此不具備 cache 或 SQL 功能。');
node_fs = {
// library_namespace.storage.read_file()
readFile : function(file_path, options, callback) {
library_namespace.error('Cannot read file ' + file_path);
if (typeof callback === 'function')
callback(true);
},
// library_namespace.storage.write_file()
writeFile : function(file_path, data, options, callback) {
library_namespace.error('Cannot write to file ' + file_path);
if (typeof options === 'function' && !callback)
callback = options;
if (typeof callback === 'function')
callback(true);
}
};
}
// --------------------------------------------------------------------------------------------
/**
* cache 相關函數:
*
* @see application.storage.file.get_cache_file
* application.OS.Windows.file.cacher
* application.net.Ajax.get_URL_cache<br />
* application.net.wiki<br />
* wiki_API.cache() CeL.wiki.cache()
*/
if (false) {
// examples
CeL.wiki.cache({
type : 'page',
file_name : 'file_name',
list : 'WP:SB',
operator : function(data) {
console.log(data);
}
}, function callback(data) {
console.log(data);
}, {
// default options === this
// namespace : '0|1',
// [KEY_SESSION]
// session : wiki,
// title_prefix : 'Template:',
// cache path prefix
prefix : 'base_directory/'
});
CeL.set_debug(6);
CeL.wiki.cache({
type : 'callback',
file_name : 'file_name',
list : function(callback) {
callback([ 1, 2, 3 ]);
},
operator : function(data) {
console.log(data);
}
}, function callback(data) {
console.log(data);
}, {
// default options === this
// namespace : '0|1',
// [KEY_SESSION]
// session : wiki,
// title_prefix : 'Template:',
// cache path prefix
prefix : './'
});
CeL.set_debug(6);
var wiki = Wiki(true);
CeL.wiki.cache({
type : 'wdq',
file_name : 'countries',
list : 'claim[31:6256]',
operator : function(list) {
// console.log(list);
result = list;
}
}, function callback(list) {
// console.log(list);
}, {
// default options === this
// namespace : '0|1',
// [KEY_SESSION]
session : wiki,
// title_prefix : 'Template:',
// cache path prefix
prefix : './'
});
}
/**
* cache 作業操作之輔助套裝函數。
*
* 注意: only for node.js. 必須自行 include 'application.platform.nodejs'。 <code>
CeL.run('application.platform.nodejs');
* </code><br />
* 注意: 需要自行先創建各 type 之次目錄,如 page, redirects, embeddedin, ...<br />
* 注意: 會改變 operation, _this Warning: will modify operation, _this!
*
* 連續作業: 依照 _this 設定 {Object}default options即傳遞於各 operator 間的 ((this))。<br />
* 依照 operation 順序個別執行單一項作業。
*
* 單一項作業流程:<br />
* 設定檔名。<br />
* 若不存在此檔,則:<br />
* >>> 依照 operation.type 與 operation.list 取得資料。<br />
* >>> 若 Array.isArray(operation.list) 則處理多項列表作業:<br />
* >>>>>> 個別處理單一項作業,每次執行 operation.each() || operation.each_retrieve()。<br />
* >>> 執行 data = operation.retrieve(data),以其回傳作為將要 cache 之 data。<br />
* >>> 寫入cache。<br />
* 執行 operation.operator(data)
*
* TODO: file_stream<br />
* TODO: do not write file
*
* @param {Object|Array}operation
* 作業設定。
* @param {Function}[callback]
* 所有作業(operation)執行完後之回調函數。 callback(response data)
* @param {Object}[_this]
* 傳遞於各 operator 間的 ((this))。注意: 會被本函數更動!
*/
function wiki_API_cache(operation, callback, _this) {
if (library_namespace.is_Object(callback) && !_this) {
// 未設定/不設定 callback
// shift arguments.
_this = callback;
callback = undefined;
}
var index = 0;
/**
* 連續作業時,轉到下一作業。
*
* node.js v0.11.16: In strict mode code, functions can only be declared
* at top level or immediately within another function.
*/
function next_operator(data) {
library_namespace.debug('處理連續作業序列,轉到下一作業: ' + (index + 1) + '/'
+ operation.length, 2, 'wiki_API_cache.next_operator');
// [ {Object}operation, {Object}operation, ... ]
// operation = { type:'embeddedin', operator:function(data) }
if (index < operation.length) {
var this_operation = operation[index++];
// console.log(this_operation);
if (!this_operation) {
// Allow null operation.
library_namespace.debug('未設定 operation[' + (index - 1)
+ ']。Skip this operation.', 1,
'wiki_API_cache.next_operator');
next_operator(data);
} else {
if (!('list' in this_operation)) {
// use previous data as list.
library_namespace.debug(
'未特別指定 list以前一次之回傳 data 作為 list。', 3,
'wiki_API_cache.next_operator');
library_namespace.debug('前一次之回傳 data: '
+ (data && JSON.stringify(data).slice(0, 180))
+ '...', 3, 'wiki_API_cache.next_operator');
this_operation.list = data;
}
if (data) {
library_namespace.debug('設定 .last_data_got: '
+ (data && JSON.stringify(data).slice(0, 180))
+ '...', 3, 'wiki_API_cache.next_operator');
this_operation.last_data_got = data;
}
// default options === _this: 傳遞於各 operator 間的 ((this))。
wiki_API_cache(this_operation, next_operator, _this);
}
} else if (typeof callback === 'function') {
if (false && Array.isArray(data)) {
// TODO: adapt to {Object}operation
library_namespace.log('wiki_API_cache: Get ' + data.length
+ ' page(s).');
// 自訂list
// data = [ '' ];
if (_this.limit >= 0) {
// 設定此初始值,可跳過之前已經處理過的。
data = data.slice(0 * _this.limit, 1 * _this.limit);
}
library_namespace.debug(data.slice(0, 8).map(
wiki_API.title_of).join('\n')
+ '\n...');
}
// last 收尾
callback.call(_this, data);
}
}
if (Array.isArray(operation)) {
next_operator();
return;
}
// ----------------------------------------------------
/**
* 以下為處理單一次作業。
*/
library_namespace.debug('處理單一次作業。', 2, 'wiki_API_cache');
library_namespace.debug(
'using operation: ' + JSON.stringify(operation), 6,
'wiki_API_cache');
if (typeof _this !== 'object') {
// _this: 傳遞於各 operator 間的 ((this))。
_this = Object.create(null);
}
var file_name = operation.file_name,
/** 前一次之回傳 data。每次產出的 data。 */
last_data_got = operation.last_data_got;
if (typeof file_name === 'function') {
// @see wiki_API_cache.title_only
file_name = file_name.call(_this, last_data_got, operation);
}
var
/** {String}method to get data */
type = operation.type,
/** {Boolean}是否自動嘗試建立目錄。 */
try_mkdir = typeof library_namespace.fs_mkdir === 'function'
&& operation.mkdir,
//
operator = typeof operation.operator === 'function'
&& operation.operator,
//
list = operation.list;
if (!file_name) {
// 若自行設定了檔名,則慢點執行 list(),先讀讀 cache。因為 list() 可能會頗耗時間。
// 基本上,設定 this.* 應該在 operation.operator() 中,而不是在 operation.list() 中。
if (typeof list === 'function') {
// TODO: 允許非同步方法。
list = list.call(_this, last_data_got, operation);
}
if (!operation.postfix) {
if (type === 'file')
operation.postfix = '.txt';
else if (type === 'URL')
operation.postfix = '.htm';
}
// 自行設定之檔名 operation.file_name 優先度較 type/title 高。
// 需要自行創建目錄!
file_name = _this[type + '_prefix'] || type;
file_name = [ file_name
// treat file_name as directory
? /[\\\/]/.test(file_name) ? file_name : file_name + '/' : '',
//
wiki_API.is_page_data(list) ? list.title
// 若 Array.isArray(list),則 ((file_name = ''))。
: typeof list === 'string' && wiki_API.normalize_title(list, true) ];
if (file_name[1]) {
file_name = file_name[0]
// 正規化檔名。
+ file_name[1].replace(/\//g, '_');
} else {
// assert: node_fs.readFile('') 將執行 callback(error)
file_name = '';
}
}
if (file_name) {
if (!('postfix' in operation) && !('postfix' in _this)
&& /\.[a-z\d\-]+$/i.test(file_name)) {
// 若已設定 filename extension則不自動添加。
operation.postfix = '';
}
file_name = [ 'prefix' in operation ? operation.prefix
// _this.prefix: cache path prefix
: 'prefix' in _this
//
? _this.prefix : wiki_API_cache.prefix, file_name,
// auto detect filename extension
'postfix' in operation ? operation.postfix
//
: 'postfix' in _this ? _this.postfix : wiki_API_cache.postfix ];
library_namespace.debug('Pre-normalized cache file name: ['
+ file_name + ']', 5, 'wiki_API_cache');
if (false)
library_namespace.debug('file name param:'
+ [ operation.file_name, _this[type + '_prefix'], type,
JSON.stringify(list) ].join(';'), 6,
'wiki_API_cache');
// 正規化檔名。
file_name = file_name.join('').replace(/[:*?<>]/g, '_');
}
library_namespace.debug('Try to read cache file: [' + file_name + ']',
3, 'wiki_API_cache');
var
/**
* 採用 JSON<br />
* TODO: parse & stringify 機制
*
* @type {Boolean}
*/
using_JSON = 'json' in operation ? operation.json : /\.json$/i
.test(file_name),
/** {String}file encoding for fs of node.js. */
encoding = _this.encoding || wiki_API.encoding;
// list file path
_this.file_name = file_name;
// console.log('Read file: ' + file_name);
node_fs.readFile(file_name, encoding, function(error, data) {
/**
* 結束作業。
*/
function finish_work(data) {
library_namespace.debug('finish work', 3,
'wiki_API_cache.finish_work');
last_data_got = data;
if (operator)
operator.call(_this, data, operation);
library_namespace.debug('loading callback', 3,
'wiki_API_cache.finish_work');
if (typeof callback === 'function')
callback.call(_this, data);
}
if (!operation.reget && !error && (data ||
// 當資料 Invalid例如採用 JSON 卻獲得空資料時;則視為 error不接受此資料。
('accept_empty_data' in _this
//
? _this.accept_empty_data : !using_JSON))) {
// gettext_config:{"id":"using-cached-data"}
library_namespace.debug('Using cached data.', 3,
'wiki_API_cache');
library_namespace.debug('Cached data: ['
+ (data && data.slice(0, 200)) + ']...', 5,
'wiki_API_cache');
if (using_JSON && data) {
try {
data = JSON.parse(data);
} catch (e) {
library_namespace.error(
// error. e.g., "undefined"
'wiki_API_cache: Cannot parse as JSON: ' + data);
// 注意: 若中途 abort此時可能需要手動刪除大小為 0 的 cache file
data = undefined;
}
}
finish_work(data);
return;
}
library_namespace.debug(
operation.reget ? 'Dispose cache. Reget again.'
// ↑ operation.reget: 放棄 cache重新取得資料。
: 'No valid cached data. Try to get data...', 3,
'wiki_API_cache');
/**
* 寫入 cache 至檔案系統。
*/
function write_cache(data) {
if (operation.cache === false) {
// 當設定 operation.cache: false 時,不寫入 cache。
library_namespace.debug(
'設定 operation.cache === false不寫入 cache。', 3,
'wiki_API_cache.write_cache');
} else if (/[^\\\/]$/.test(file_name)) {
library_namespace.info('wiki_API_cache: '
+ 'Write cache data to [' + file_name + '].'
+ (using_JSON ? ' (using JSON)' : ''));
library_namespace.debug('Cache data: '
+ (data && JSON.stringify(data).slice(0, 190))
+ '...', 3, 'wiki_API_cache.write_cache');
var write = function() {
// 為了預防需要建立目錄,影響到後面的作業,
// 因此採用 fs.writeFileSync() 而非 fs.writeFile()。
node_fs.writeFileSync(file_name, using_JSON ? JSON
.stringify(data) : data, encoding);
};
try {
write();
} catch (error) {
// assert: 此 error.code 表示上層目錄不存在。
var matched = error.code === 'ENOENT'
// 未設定 operation.mkdir 的話,預設會自動嘗試建立目錄。
&& try_mkdir !== false
//
&& file_name.match(/[\\\/][^\\\/]+$/);
if (matched) {
// 僅測試一次。設定 "已嘗試過" flag。
try_mkdir = false;
// create parent directory
library_namespace.fs_mkdir(file_name.slice(0,
matched.index));
// re-write file again.
try {
write();
} catch (e) {
library_namespace.error(
//
'wiki_API_cache: Error to write cache data!');
library_namespace.error(e);
}
}
}
}
finish_work(data);
}
// node.js v0.11.16: In strict mode code, functions can only be
// declared
// at top level or immediately within another function.
/**
* 取得並處理下一項 data。
*/
function get_next_item(data) {
if (index < list.length) {
// 利用基本相同的參數以取得 cache。
_operation.list = list[index++];
var message = '處理多項列表作業: ' + type + ' ' + index + '/'
+ list.length;
if (list.length > 8) {
library_namespace.info('wiki_API_cache.get_next_item: '
+ message);
} else {
library_namespace.debug(message, 1,
'wiki_API_cache.get_next_item');
}
wiki_API_cache(_operation, get_next_item, _this);
} else {
// last 收尾
// All got. retrieve data.
if (_operation.data_list)
data = _operation.data_list;
if (typeof operation.retrieve === 'function')
data = operation.retrieve.call(_this, data);
write_cache(data);
}
}
if (typeof list === 'function' && type !== 'callback') {
library_namespace.debug('Call .list()', 3, 'wiki_API_cache');
list = list.call(_this, last_data_got, operation);
// 對於 .list() 為 asynchronous 函數的處理。
if (list === wiki_API_cache.abort) {
library_namespace.debug('It seems the .list()'
+ ' is an asynchronous function.' + ' I will exit'
+ ' and wait for the .list() finished.', 3,
'wiki_API_cache');
return;
}
}
if (list === wiki_API_cache.abort) {
library_namespace
.debug('Abort operation.', 1, 'wiki_API_cache');
finish_work();
return;
}
if (Array.isArray(list)) {
if (!type) {
library_namespace.debug('採用 list (length ' + list.length
+ ') 作為 data。', 1, 'wiki_API_cache');
write_cache(list);
return;
}
if (list.length > 1e6) {
library_namespace.warn(
//
'wiki_API_cache: 警告: list 過長/超過限度 (length ' + list.length
+ '),將過於耗時而不實際!');
}
/**
* 處理多項列表作業。
*/
var index = 0, _operation = Object.clone(operation);
// 個別頁面不設定 .file_name, .end。
delete _operation.end;
if (_operation.each_file_name) {
_operation.file_name = _operation.each_file_name;
delete _operation.each_file_name;
} else {
delete _operation.file_name;
}
if (typeof _operation.each === 'function') {
// 每一項 list 之項目執行一次 .each()。
_operation.operator = _operation.each;
delete _operation.each;
} else {
if (typeof _operation.each_retrieve === 'function')
_operation.each_retrieve = _operation.each_retrieve
.bind(_this);
else
delete _operation.each_retrieve;
/**
* 預設處理列表的函數。
*/
_operation.operator = function(data) {
if ('each_retrieve' in operation)
// 資料事後處理程序 (post-processor):
// 將以 .each_retrieve() 的回傳作為要處理的資料。
data = operation.each_retrieve.call(_this, data);
if (_operation.data_list) {
if (Array.isArray(data))
Array.prototype.push.apply(
_operation.data_list, data);
else if (data)
_operation.data_list.push(data);
} else {
if (Array.isArray(data))
_operation.data_list = data;
else if (data)
_operation.data_list = [ data ];
}
};
}
library_namespace.debug('處理多項列表作業, using operation: '
+ JSON.stringify(_operation), 5, 'wiki_API_cache');
get_next_item();
return;
}
// ------------------------------------------------
/**
* 以下為處理單一項作業。
*/
var to_get_data, list_type;
if (// type in get_list.type
wiki_API.list.type_list.includes(type)) {
list_type = type;
type = 'list';
}
switch (type) {
case 'callback':
if (typeof list !== 'function') {
library_namespace
.warn('wiki_API_cache: list is not function!');
callback.call(_this, last_data_got);
break;
}
// 手動取得資料。使用 list=function(callback){callback(list);}
to_get_data = function(list, callback) {
library_namespace.log('wiki_API_cache: '
+ 'manually get data and then callback(list).');
if (typeof list === 'function') {
// assert: (typeof list === 'function') 必須自己回 call
list.call(_this, callback, last_data_got, operation);
}
};
break;
case 'file':
// 一般不應用到。
// get file 內容。
to_get_data = function(file_path, callback) {
library_namespace.log('wiki_API_cache: Get file ['
+ file_path + '].');
node_fs.readFile(file_path, operation.encoding, function(
error, data) {
if (error)
library_namespace.error(
//
'wiki_API_cache: Error get file [' + file_path
+ ']: ' + error);
callback.call(_this, data);
});
};
break;
case 'URL':
// get URL 頁面內容。
to_get_data = function(URL, callback) {
library_namespace.log('wiki_API_cache: Get URL of [' + URL
+ '].');
library_namespace.get_URL(URL, callback);
};
break;
case 'wdq':
to_get_data = function(query, callback) {
if (_this[KEY_SESSION]) {
if (!_this[KEY_SESSION].data_session) {
_this[KEY_SESSION].set_data();
_this[KEY_SESSION].run(function() {
// retry again
to_get_data(query, callback);
});
return;
}
operation[KEY_SESSION]
//
= _this[KEY_SESSION].data_session;
}
library_namespace.log('wiki_API_cache: Wikidata Query ['
+ query + '].');
// wikidata_query(query, callback, options)
wiki_API.wdq(query, callback, operation);
};
break;
case 'page':
// get page contents 頁面內容。
// title=(operation.title_prefix||_this.title_prefix)+operation.list
to_get_data = function(title, callback) {
library_namespace.log('wiki_API_cache: Get content of '
+ wiki_API.title_link_of(title));
// 防止汙染。
var _options = library_namespace.new_options(_this,
operation);
// 包含 .list 時wiki_API.page() 不會自動添加 .prop。
delete _options.list;
wiki_API.page(title, function(page_data) {
callback(page_data);
}, _options);
};
break;
case 'redirects_here':
// 取得所有重定向到(title重定向標的)之頁面列表,(title重定向標的)將會排在[0]。
// 注意: 無法避免雙重重定向問題!
to_get_data = function(title, callback) {
// wiki_API.redirects_here(title, callback, options)
wiki_API.redirects_here(title, function(root_page_data,
redirect_list) {
if (!operation.keep_redirects && redirect_list
&& redirect_list[0]) {
if (false) {
console.assert(redirect_list[0].redirects
//
.join() === redirect_list.slice(1).join());
}
// cache 中不需要此累贅之資料。
delete redirect_list[0].redirects;
delete redirect_list[0].redirect_list;
}
callback(redirect_list);
}, Object.assign({
// Making .redirect_list[0] the redirect target.
include_root : true
}, _this, operation));
};
break;
case 'list':
to_get_data = function(title, callback) {
var options = Object.assign({
type : list_type
}, _this, operation);
wiki_API.list(title, function(pages) {
if (!options.for_each_page || options.get_list) {
library_namespace.log(list_type
// allpages 不具有 title。
+ (title ? ' '
//
+ wiki_API.title_link_of(title) : '') + ': '
+ pages.length + ' page(s).');
}
pages.query_title = title;
// page list, title page_data
callback(pages);
}, options);
};
break;
default:
if (typeof type === 'function')
to_get_data = type.bind(Object.assign(Object.create(null),
_this, operation));
else if (type)
throw new Error('wiki_API_cache: Bad type: ' + type);
else {
library_namespace.debug('直接採用 list 作為 data。', 1,
'wiki_API_cache');
write_cache(list);
return;
}
}
// 回復 recover type
// if (list_type) type = list_type;
var title = list;
if (typeof title === 'string') {
// 可以用 operation.title_prefix 覆蓋 _this.title_prefix
if ('title_prefix' in operation) {
if (operation.title_prefix)
title = operation.title_prefix + title;
} else if (_this.title_prefix)
title = _this.title_prefix + title;
}
library_namespace.debug('處理單一項作業: ' + wiki_API.title_link_of(title)
+ '。', 3, 'wiki_API_cache');
to_get_data(title, write_cache);
});
}
/** {String}預設 file encoding for fs of node.js。 */
wiki_API.encoding = 'utf8';
/** {String}檔名預設前綴。 */
wiki_API_cache.prefix = '';
/** {String}檔名預設後綴。 */
wiki_API_cache.postfix = '.json';
/**
* 若 operation.list() return wiki_API_cache.abort<br />
* 則將直接中斷離開 operation不執行 callback。<br />
* 此時須由 operation.list() 自行處理 callback。
*/
wiki_API_cache.abort = typeof Symbol === 'function' ? Symbol('ABORT_CACHE')
//
: {
cache : 'abort'
};
/**
* 只取檔名,僅用在 operation.each_file_name。<br />
* <code>{
* each_file_name : CeL.wiki.cache.title_only,
* }</code>
*
* @type {Function}
*/
wiki_API_cache.title_only = function(last_data_got, operation) {
var list = operation.list;
if (typeof list === 'function') {
operation.list = list = list.call(this, last_data_got, operation);
}
return operation.type + '/' + remove_page_title_namespace(list);
};
// ------------------------------------------------------------------------
// export 導出.
// wiki_API.cache = wiki_API_cache;
return wiki_API_cache;
}

6212
app/node_modules/cejs/application/net/wiki/data.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

1646
app/node_modules/cejs/application/net/wiki/edit.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,483 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): 特色內容特設功能。
*
* 注意: 本程式庫必須應各wiki特色內容改動而改寫。
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* @example <code>
CeL.run('application.net.wiki.featured_content');
wiki.get_featured_content('FFA', function(FC_data_hash) {});
wiki.get_featured_content('GA', function(FC_data_hash) {});
wiki.get_featured_content('FA', function(FC_data_hash) {});
wiki.get_featured_content('FL', function(FC_data_hash) {});
</code>
*
* @since 2020/1/22 9:18:43
*/
// 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.featured_content',
require : 'data.native.' + '|application.net.wiki.'
// load MediaWiki module basic functions
+ '|application.net.wiki.namespace.'
// for to_exit
+ '|application.net.wiki.parser.'
//
+ '|application.net.wiki.page.|application.net.wiki.list.',
// 設定不匯出的子函式。
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;
// @inner
// var is_api_and_title = wiki_API.is_api_and_title,
// normalize_title_parameter = wiki_API.normalize_title_parameter;
var to_exit = wiki_API.parser.parser_prototype.each.exit;
// --------------------------------------------------------------------------------------------
function featured_content() {
}
function get_parsed(page_data) {
if (!page_data)
return;
var parsed = typeof page_data.each === 'function'
// `page_data` is parsed data
? page_data : wiki_API.parser(page_data);
return parsed;
}
// ------------------------------------------------------------------------
/** 特色內容為列表 */
var KEY_IS_LIST = 'is_list';
/** 為已撤銷的特色內容 */
var KEY_ISFFC = 'is_former';
/** 特色內容類別 */
var KEY_CATEGORY = 'category';
/** 指示用。會在 parse_each_zhwiki_FC_item_list_page() 之後就刪除。 */
var KEY_LIST_PAGE = 'list page';
function remove_KEY_LIST_PAGE(FC_data_hash) {
for ( var title in FC_data_hash) {
delete FC_data_hash[title][KEY_LIST_PAGE];
}
}
var featured_content_configurations = {
zhwiki : {
// @see [[Category:特色内容]]
list_source : {
FA : '典范条目‎',
FL : '特色列表‎',
FP : '特色图片‎',
GA : '優良條目‎',
},
get_FC : /* get_zhwiki_FC_via_list_page */get_FC_via_category
},
jawiki : {
// @see [[ja:Category:記事の選考]]
list_source : {
FA : 'ウィキペディア 秀逸な記事',
FL : 'ウィキペディア 秀逸な一覧',
FP : 'ウィキペディア 秀逸な画像',
GA : 'ウィキペディア 良質な記事'
},
get_FC : get_FC_via_category
},
enwiki : {
// @see [[en:Category:Featured content]]
list_source : {
FFA : {
page : 'Wikipedia:Former featured articles',
handler : parse_enwiki_FFA
},
DGA : 'Delisted good articles',
FA : 'Featured articles',
FL : 'Featured lists',
FP : 'Featured pictures',
FT : 'Featured topics',
GA : 'Good articles'
},
get_FC : get_FC_via_category
}
};
function get_site_configurations(session) {
// e.g., 'zhwiki'
var site_name = wiki_API.site_name(session);
var FC_configurations = featured_content_configurations[site_name];
return FC_configurations;
}
// @see 20190101.featured_content_maintainer.js
// 注意: 這邊尚未處理 redirects 的問題!!
function parse_each_zhwiki_FC_item_list_page(page_data, redirects_to_hash,
sub_FC_list_pages) {
var using_GA = options.type === 'GA';
/** {String}將顯示的類型名稱。 */
var TYPE_NAME = using_GA ? '優良條目' : '特色內容';
/** {Array}錯誤記錄 */
var error_logs = [];
var FC_data_hash = this.FC_data_hash
// FC_data_hash[redirected FC_title] = { FC_data }
|| (this.FC_data_hash = Object.create(null));
/**
* {String}page title = page_data.title
*/
var title = wiki_API.title_of(page_data);
/**
* {String}page content, maybe undefined. 條目/頁面內容 =
* wiki_API.revision_content(revision)
*/
var content = wiki_API.content_of(page_data);
//
var matched;
/** 特色內容為列表 */
var is_list = /list|列表/.test(title)
// e.g., 'Wikipedia:FL'
|| /:[DF]?[FG]L/.test(page_data.original_title || title),
// 本頁面為已撤消的條目列表。注意: 這包含了被撤銷後再次被評為典範的條目。
is_FFC = [ page_data.original_title, title ].join('|');
// 對於進階的條目,採用不同的 is_FFC 表示法。
is_FFC = using_GA && /:FF?A/.test(is_FFC) && 'UP'
|| /:[DF][FG][AL]|已撤消的|已撤销的/.test(is_FFC);
if (is_FFC) {
// 去掉被撤銷後再次被評為典範的條目/被撤銷後再次被評為特色的列表/被撤銷後再次被評選的條目
content = content.replace(/\n== *(?:被撤銷後|被撤销后)[\s\S]+$/, '');
}
// 自動偵測要使用的模式。
function test_pattern(pattern, min) {
var count = 0, matched;
while (matched = pattern.exec(content)) {
if (matched[1] && count++ > (min || 20)) {
return pattern;
}
}
}
var catalog,
// matched: [ all, link title, display, catalog ]
PATTERN_Featured_content = test_pattern(
// @see [[Template:FA number]] 被標記為粗體的條目已經在作為典範條目時在首頁展示過
// 典範條目, 已撤銷的典範條目, 已撤销的特色列表: '''[[title]]'''
// @see PATTERN_category
/'''\[\[([^{}\[\]\|<>\t\n#<23>]+)(?:\|([^\[\]\|<7C>]*))?\]\]'''|\n==([^=].*?)==\n/g)
// 特色列表: [[:title]]
|| test_pattern(/\[\[:([^{}\[\]\|<>\t\n#<23>]+)(?:\|([^\[\]\|<7C>]*))?\]\]|\n==([^=].*?)==\n/g)
// 優良條目轉換到子頁面模式: 警告:本頁中的所有嵌入頁面都會被機器人當作優良條目的分類列表。請勿嵌入非優良條目的分類列表。
|| test_pattern(/{{(Wikipedia:[^{}\|]+)}}/g, 10)
// 優良條目子分類列表, 已撤消的優良條目: all links NOT starting with ':'
|| /\[\[([^{}\[\]\|<>\t\n#<23>:][^{}\[\]\|<>\t\n#<23>]*)(?:\|([^\[\]\|<7C>]*))?\]\]|\n===([^=].*?)===\n/g;
library_namespace.log(wiki_API.title_link_of(title)
+ ': '
+ (is_FFC ? 'is former'
+ (is_FFC === true ? '' : ' (' + is_FFC + ')')
: 'NOT former') + ', '
+ (is_list ? 'is list' : 'is article') + ', using pattern '
+ PATTERN_Featured_content);
// reset pattern
PATTERN_Featured_content.lastIndex = 0;
// 分類/類別。
if (matched = title.match(/\/(?:分類|分类)\/([^\/]+)/)) {
catalog = matched[1];
}
if (false) {
library_namespace.log(content);
console.log([ page_data.original_title || title, is_FFC, is_list,
PATTERN_Featured_content ]);
}
while (matched = PATTERN_Featured_content.exec(content)) {
// 還沒繁簡轉換過的標題。
var original_FC_title = wiki_API.normalize_title(matched[1]);
if (matched.length === 2) {
sub_FC_list_pages.push(original_FC_title);
continue;
}
// assert: matched.length === 4
if (matched[3]) {
// 分類/類別。
catalog = matched[3].replace(/<!--[\s\S]*?-->/g, '').trim()
.replace(/\s*\d+$/, '');
continue;
}
// 去除並非文章,而是工作連結的情況。 e.g., [[File:文件名]], [[Category:维基百科特色内容|*]]
if (this.namespace(original_FC_title, 'is_page_title') !== 0) {
continue;
}
// 轉換成經過繁簡轉換過的最終標題。
var FC_title = redirects_to_hash
&& redirects_to_hash[original_FC_title]
|| original_FC_title;
if (FC_title in FC_data_hash) {
// 基本檢測與提醒。
if (FC_data_hash[FC_title][KEY_ISFFC] === is_FFC) {
library_namespace.warn(
//
'parse_each_zhwiki_FC_item_list_page: Duplicate '
+ TYPE_NAME + ' title: ' + FC_title + '; '
+ JSON.stringify(FC_data_hash[FC_title]) + '; '
+ matched[0]);
error_logs.push(wiki_API.title_link_of(title)
+ '有重複條目: '
+ wiki_API.title_link_of(original_FC_title)
+ (original_FC_title === FC_title ? '' : ', '
+ wiki_API.title_link_of(FC_title)));
} else if (!!FC_data_hash[FC_title][KEY_ISFFC] !== !!is_FFC
&& (FC_data_hash[FC_title][KEY_ISFFC] !== 'UP' || is_FFC !== false)) {
error_logs
.push(wiki_API.title_link_of(FC_title)
+ ' 被同時列在了現存及已撤銷的'
+ TYPE_NAME
+ '清單中: '
+ wiki_API.title_link_of(original_FC_title)
+ '@'
+ wiki_API.title_link_of(title)
+ ', '
+ wiki_API
.title_link_of(FC_data_hash[FC_title][KEY_LIST_PAGE][1])
+ '@'
+ wiki_API
.title_link_of(FC_data_hash[FC_title][KEY_LIST_PAGE][0]));
library_namespace.error(wiki_API.title_link_of(FC_title)
+ ' 被同時列在了現存及已撤銷的' + TYPE_NAME + '清單中: ' + is_FFC
+ '; ' + JSON.stringify(FC_data_hash[FC_title]));
}
}
var FC_data = FC_data_hash[FC_title] = Object.create(null);
FC_data[KEY_IS_LIST] = is_list;
FC_data[KEY_ISFFC] = is_FFC;
if (catalog)
FC_data[KEY_CATEGORY] = catalog;
FC_data[KEY_LIST_PAGE] = [ title, original_FC_title ];
}
return error_logs;
}
function get_zhwiki_FC_via_list_page(options, callback) {
var session = this;
var using_GA = options.type === 'GA';
var FC_list_pages = (using_GA ? 'WP:GA' : 'WP:FA|WP:FL').split('|');
var Former_FC_list_pages = (using_GA ? 'WP:DGA|WP:FA|WP:FFA'
: 'WP:FFA|WP:FFL').split('|');
var page_options = {
redirects : 1,
multi : true
};
this.page(FC_list_pages.concat(Former_FC_list_pages), function(
page_data_list) {
var sub_FC_list_pages = [];
page_data_list.forEach(function(page_data) {
parse_each_zhwiki_FC_item_list_page.call(session, page_data,
options.redirects_to_hash, sub_FC_list_pages);
});
if (sub_FC_list_pages.length === 0) {
remove_KEY_LIST_PAGE(session.FC_data_hash);
callback && callback(session.FC_data_hash);
return;
}
session.page(sub_FC_list_pages, function(page_data_list) {
page_data_list.forEach(function(page_data) {
parse_each_zhwiki_FC_item_list_page.call(session,
page_data, options.redirects_to_hash);
});
remove_KEY_LIST_PAGE(session.FC_data_hash);
callback && callback(session.FC_data_hash);
}, page_options);
}, page_options);
}
// ------------------------------------------------------------------------
function parse_enwiki_FFA(page_data, type_name) {
/**
* {String}page content, maybe undefined. 條目/頁面內容 =
* wiki_API.revision_content(revision)
*/
var content = wiki_API.content_of(page_data);
content = content.replace(/^[\s\S]+?\n(==.+?==)/, '$1')
// remove == Former featured articles that have been re-promoted ==
.replace(/==\s*Former featured articles.+?==[\s\S]*$/, '');
var FC_data_hash = this.FC_data_hash;
var PATTERN_Featured_content = /\[\[(.+?)\]\]/g, matched;
while (matched = PATTERN_Featured_content.exec(content)) {
var FC_title = matched[1];
var FC_data = FC_data_hash[FC_title];
if (FC_data) {
if (!FC_data.types.includes(type_name)) {
// 把重要的放在前面。
FC_data.types.unshift(type_name);
}
// Do not overwrite
continue;
}
FC_data = FC_data_hash[FC_title] = {
type : type_name,
types : [ type_name ]
};
FC_data[KEY_ISFFC] = true;
// FC_data[KEY_IS_LIST] = is_list;
}
}
// ------------------------------------------------------------------------
function normalize_type_name(type) {
return type;
}
function get_FC_via_category(options, callback) {
var FC_configurations = get_site_configurations(this);
var type_name = normalize_type_name(options.type);
var list_source = FC_configurations.list_source[type_name];
// console.trace([ FC_configurations, type_name, list_source ]);
if (!list_source) {
throw new Error('Unknown type: ' + options.type);
}
// ----------------------------
var FC_data_hash = this.FC_data_hash
// FC_data_hash[redirected FC_title] = { FC_data }
|| (this.FC_data_hash = Object.create(null));
// ----------------------------
var session = this;
if (list_source.page) {
this.page(list_source.page, function(page_data) {
list_source.handler.call(session, page_data, type_name);
callback && callback(FC_data_hash);
});
return;
}
// ----------------------------
var category_title = list_source;
/** 特色內容為列表 */
var is_list = /list|列表/.test(category_title);
wiki_API.list(category_title, function(list/* , target, options */) {
list.forEach(function(page_data) {
var FC_title = page_data.title;
var FC_data = FC_data_hash[FC_title];
if (!FC_data) {
FC_data = FC_data_hash[FC_title] = {
type : type_name,
types : [ type_name ]
};
} else if (FC_data.type !== type_name) {
if (FC_data.type !== 'FFA' || type_name === 'FA') {
if (options.on_conflict) {
options.on_conflict(FC_title, {
from : FC_data.type,
to : type_name,
category : category_title
});
} else {
library_namespace.warn('get_FC_via_category: '
+ FC_title + ': ' + FC_data.type + '→'
+ type_name);
}
}
if (!FC_data.types.includes(type_name)) {
// 把重要的放在前面。
FC_data.types.unshift(type_name);
}
FC_data.type = type_name;
}
FC_data[KEY_IS_LIST] = is_list;
// FC_data[KEY_ISFFC] = false;
// if (catalog) FC_data[KEY_CATEGORY] = catalog;
});
callback && callback(FC_data_hash);
}, {
// [KEY_SESSION]
session : this,
// namespace: '0|1',
type : 'categorymembers'
});
}
// --------------------------------------------------------------------------------------------
// export 導出.
// Object.assign(featured_content, {});
// ------------------------------------------------------------------------
// wrapper for local function
wiki_API.prototype.get_featured_content_configurations = function get_featured_content_configurations() {
return get_site_configurations(this);
};
// callback(wiki.FC_data_hash);
// e.g.,
// wiki.FC_data_hash[title]={type:'GA',types:['GA','FFA'],is_former:true,is_list:false}
wiki_API.prototype.get_featured_content = function get_featured_content(
options, callback) {
var FC_configurations = this.get_featured_content_configurations();
var get_FC_function = FC_configurations && FC_configurations.get_FC;
if (!get_FC_function) {
library_namespace.error('get_featured_content: '
+ 'Did not configured how to get featured content! '
+ wiki_API.site_name(this));
return;
}
if (typeof options === 'string') {
options = {
type : options
};
} else {
options = library_namespace.setup_options(options);
}
get_FC_function.call(this, options, callback);
};
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
// return featured_content;
}

2859
app/node_modules/cejs/application/net/wiki/list.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

4003
app/node_modules/cejs/application/net/wiki/namespace.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

4723
app/node_modules/cejs/application/net/wiki/page.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

320
app/node_modules/cejs/application/net/wiki/page/Page.js generated vendored Normal file
View File

@@ -0,0 +1,320 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): Page
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since
*
* @see https://mwn.toolforge.org/docs/interfaces/_page_.mwnpage.html
*/
// 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.page.Page',
require : 'data.code.compatibility.'
//
+ '|application.net.wiki.page.'
//
+ '|application.net.wiki.list.',
// 設定不匯出的子函式。
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;
// @inner
// var get_namespace = wiki_API.namespace;
// ------------------------------------------------------------------------
if (false) {
// call new_Page()
page = wiki_session.Page(page_title);
// {Number}p.ns
// {String}p.title
// await page.backlinks({get_list:true}) will get {Array}list.
// page.backlinks() is asyncIterator
//
// https://www.codementor.io/@tiagolopesferreira/asynchronous-iterators-in-javascript-jl1yg8la1
// https://stackoverflow.com/questions/55531247/using-javascripts-symbol-asynciterator-with-for-await-of-loop
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
// for await (const page_data of page.backlinks()) {
// console.log(page_data); }
// TODO:
// typeof await(page.content() || page.read()) === 'string';
typeof page.wikitext === 'string';
// page.fetch() is asyncIterator 泛用方法 相當於 wiki.query()
// page.revisions() is asyncIterator
// typeof await(page.is_biography()) === 'boolean';
}
function Page(page_title, options, session) {
this[KEY_SESSION] = session;
if (wiki_API.is_page_data(page_title)) {
Object.assign(this, page_title);
return;
}
// page_data 之 structure 按照 wiki API 本身之 return
// page_data = {pageid,ns,title,revisions:[{revid,timestamp,'*'}]}
Object.assign(this, {
// pageid : 0,
ns : session.namespace(page_title) || 0,
title : session.normalize_title(page_title)
});
}
function set_options_session(options) {
var session = this[KEY_SESSION];
options = wiki_API.add_session_to_options(session, options);
return options;
}
// ------------------------------------------------------------------------
function Page__content(options) {
options = set_options_session.call(this, options);
if (this.revisions) {
return wiki_API.content_of(this, options);
}
var promise = new Promise(function executor(resolve, reject) {
wiki_API.page(wiki_API.is_page_data(this) ? this : this.title,
//
function(page_data, error) {
if (error) {
reject(error);
return;
}
if (!page_data.revisions) {
reject(new Error('No .revisions get!'));
return;
}
Object.assign(this, page_data);
// console.trace(this);
return resolve(wiki_API.content_of(this, options));
}.bind(this), options);
}.bind(this));
return promise;
}
// ------------------------------------------------------------------------
function Page__check_stop(options) {
// Copy from wiki_API.prototype.next.edit
var promise = new Promise(function executor(resolve, reject) {
var session = this[KEY_SESSION];
options.token = session.token;
wiki_API.check_stop(function(stopped) {
session.stopped = stopped;
resolve(stopped);
}, options);
}.bind(this));
return promise;
}
function Page__edit(content, options) {
options = set_options_session.call(this, options);
var session = this[KEY_SESSION];
// Copy from wiki_API.prototype.next.edit
var promise = new Promise(function executor(resolve, reject) {
if (session.stopped && !options.skip_stopped) {
library_namespace.warn('Page__edit: 已停止作業,放棄編輯'
+ wiki_API.title_link_of(this) + '');
reject(new Error('放棄編輯'));
return;
}
if (this.is_Flow) {
reject(new Error(new Error('NYI: flow page')));
}
if (options.skip_nochange
// 採用 skip_nochange 可以跳過實際 edit 的動作。
&& content === wiki_API.content_of(this)) {
library_namespace.debug('Skip '
//
+ wiki_API.title_link_of(this)
// 'nochange', no change
+ ': The same content.', 1, 'Page__edit');
resolve('The same content');
return;
}
// console.trace([ this, wiki_API.is_page_data(this), session.token
// ]);
wiki_API.edit([ session.API_URL,
//
wiki_API.is_page_data(this) ? this : this.title ], content,
//
session.token, options, function wiki_API_Page_edit_callback(title,
error, result) {
if (error) {
reject(error);
return;
}
resolve(result);
});
}.bind(this));
if (!('stopped' in session)) {
promise = Page__check_stop.call(this, options).then(promise);
}
if (typeof content === 'function')
return this.content().then(promise);
return promise;
}
// ------------------------------------------------------------------------
function Page__list(options) {
options = set_options_session.call(this, options);
// options.type, options[KEY_SESSION] are setted in Page__list_async()
var promise = new Promise(function executor(resolve, reject) {
wiki_API.list(wiki_API.is_page_data(this) ? this : this.title,
//
function(pages, target, options) {
if (pages.error)
reject(pages.error);
else
resolve(pages);
}, options);
}.bind(this));
return promise;
}
var Symbol_asyncIterator = typeof Symbol === 'function'
&& Symbol.asyncIterator;
var done_object = {
// value : generator.page_count,
done : true
};
function Page__list_async(method, options) {
options = set_options_session.call(this, options);
options.type = method;
if (!Symbol_asyncIterator || options && options.get_list) {
return Page__list.call(this, options);
}
// --------------------------------------
var list_generator = Object.create(null);
list_generator[Symbol_asyncIterator] = (function() {
function get_next_object() {
return {
value : generator.queue.shift(),
done : false
};
}
var generator = {
queue : [],
next : function() {
if (generator.resolve) {
throw new Error(
'Call resolve() before latest promise resolved');
}
if (generator.queue.length > 0) {
// 執行順序3: 中間最多的是這個程序一直反覆 loop
return Promise.resolve(get_next_object());
}
// assert: generator.queue.length === 0
if (generator.done) {
// 執行順序4: 最後一次 iterate
return Promise.resolve(done_object);
}
// 執行順序1
return new Promise(function(resolve, reject) {
generator.resolve = resolve;
});
}
};
options.for_each_page = function(item) {
generator.queue.push(item);
var resolve = generator.resolve;
if (resolve) {
delete generator.resolve;
// 執行順序2
resolve(get_next_object());
}
};
wiki_API.list(wiki_API.is_page_data(this) ? this : this.title,
//
function(list) {
// generator.page_count = list.length;
generator.done = true;
var resolve = generator.resolve;
if (resolve) {
// 基本上不會執行到這邊 @ node.js
delete generator.resolve;
resolve(done_object);
}
}, options);
return generator;
}).bind(this);
return list_generator;
}
// ------------------------------------------------------------------------
// export 導出.
Object.assign(wiki_API.prototype, {
new_Page : function new_Page(page_title, options) {
return new Page(page_title, options,/* session */this);
},
Page : Page
});
wiki_API.list.type_list.forEach(function(method) {
// if (!method.includes('all'))
Page.prototype[method] = function Page__list_frontend(options) {
return Page__list_async.call(this, method, options);
};
});
Object.assign(Page.prototype, {
content : Page__content,
edit : Page__edit
});
return Page;
}

2078
app/node_modules/cejs/application/net/wiki/parser.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2145
app/node_modules/cejs/application/net/wiki/parser/misc.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1109
app/node_modules/cejs/application/net/wiki/query.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

4305
app/node_modules/cejs/application/net/wiki/task.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): 常用模板特設功能。本工具檔放置的是指定 wiki
* 計畫特有的模板。
*
* 注意: 本程式庫必須應各 wiki project 模板內容改動而改寫。
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2021/1/24 16:6:50
*/
// 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.template_functions.enwiki',
require : 'data.native.'
// Should also load essential MediaWiki modules
+ '|application.net.wiki.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki;
// @inner
// var is_api_and_title = wiki_API.is_api_and_title,
// normalize_title_parameter = wiki_API.normalize_title_parameter;
var to_exit = wiki_API.parser.parser_prototype.each.exit;
// e.g., 'zhwiki'
var module_site_name = this.id.match(/[^.]+$/)[0];
function empty_string(/* options */) {
// var template_token = this;
return '';
}
// --------------------------------------------------------------------------------------------
// template_token.expand() 可將模板轉換成一般 wiki 語法。
// https://www.mediawiki.org/w/api.php?action=help&modules=expandtemplates
// 用於 function preprocess_section_link_token()。
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors().
function expand_template_Football_box(options) {
var parameters = this.parameters;
// [[Module:Football box]]
return '<div id="' + (parameters.id || '') + '">'
// TODO: The content is skipped.
+ '</div>';
}
function parse_template_Football_box(template_token, index, parent, options) {
template_token.expand = expand_template_Football_box;
}
// --------------------------------------------------------------------------------------------
// export 導出.
wiki_API.template_functions.functions_of_site[module_site_name] = {
// 一些會添加 anchors 的特殊模板。
'Football box' : parse_template_Football_box
};
// --------------------------------------------------------------------------------------------
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}

View File

@@ -0,0 +1,595 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科):
* 常用模板特設功能。本工具檔放置的是幾乎所有wiki計畫通用的模板或者少數wiki計畫特有、且大量使用的著名模板。
*
* 注意: 本程式庫必須應各 wiki project 模板內容改動而改寫。
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* <code>
TODO:
[[w:en:Wikipedia:Database reports/Templates transcluded on the most pages]]
[[w:en:Wikipedia:High-risk templates]]
</code>
*
* @see [[Special:MostTranscludedPages]], [[Template:High-use]]
*
* @since 2021/1/24 16:6:50
*/
// 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.template_functions.general_functions',
require : 'data.native.'
// Should also load essential MediaWiki modules
+ '|application.net.wiki.template_functions.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki;
// @inner
// var is_api_and_title = wiki_API.is_api_and_title,
// normalize_title_parameter = wiki_API.normalize_title_parameter;
var to_exit = wiki_API.parser.parser_prototype.each.exit;
function empty_string(/* options */) {
// var template_token = this;
return '';
}
function expand_template_get_parameter_1(options) {
var parameters = this.parameters;
return parameters[1] ? parameters[1].toString() : '';
}
function trim_param(param) {
return param && param.toString().trim();
}
// --------------------------------------------------------------------------------------------
// template_token.expand() 可將模板轉換成一般 wiki 語法。
// https://www.mediawiki.org/w/api.php?action=help&modules=expandtemplates
// 用於 function preprocess_section_link_token()。
// --------------------------------------------------------------------------------------------
function expand_template_Void(options) {
return '';
}
function parse_template_Void(template_token, index, parent, options) {
template_token.expand = expand_template_Void;
}
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors()
// @ zh.moegirl [[FLOWERS(Innocent Grey)]]
function parse_template_Center(template_token) {
template_token.expand = expand_template_get_parameter_1;
}
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors()
// @ zh.moegirl [[ARGONAVIS from BanG Dream! 翻唱曲列表]]
function parse_template_Font(template_token) {
template_token.expand = expand_template_get_parameter_1;
}
// --------------------------------------------------------------------------------------------
// {{color|英文顏色名稱或是RGB 16進制編碼|文字}}
function expand_template_Color(options) {
var parameters = this.parameters;
return '<span style="color:' + (parameters[1] || '') + '">'
+ (parameters[2] || parameters[1] || '') + '</span>';
}
function parse_template_Color(template_token) {
template_token.expand = expand_template_Color;
}
// --------------------------------------------------------------------------------------------
function expand_template_At(options) {
var parameters = this.parameters;
return '[[File:At_sign.svg|' + (parameters[1] || 15) + 'px|link=]]';
}
function parse_template_At(template_token) {
template_token.expand = expand_template_At;
}
// --------------------------------------------------------------------------------------------
function expand_template_User_link(options) {
var parameters = this.parameters;
return '[[User:' + (parameters[1]) + '|'
+ (parameters[2] || parameters[1]) + ']]';
}
function parse_template_User_link(template_token) {
template_token.expand = expand_template_User_link;
}
// --------------------------------------------------------------------------------------------
function expand_module_If_empty(options) {
/* const */var token = options && options.template_token_called
|| this;
/* const */var parameters = token.parameters;
// Error.stackTraceLimit = Infinity;
// console.trace([ this, parameters, options ]);
// console.trace(options && options.template_token_called);
for (/* let */var index = 0; index < token.length; index++) {
var value = parameters[index];
if (value)
return value;
}
return '';
}
function parse_module_If_empty(token) {
token.expand = expand_module_If_empty;
}
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors()
// @ zh.moegirl [[ARGONAVIS from BanG Dream! 翻唱曲列表]]
function expand_template_Colored_link(options) {
var parameters = this.parameters;
// {{Colored link|顏色|頁面名稱鏈接|顯示名稱}}]
return '[[:' + parameters[2] + '|<span style="color:' + parameters[1]
+ '">' + (parameters[3] || parameters[2]) + '</span>]]';
}
function parse_template_Colored_link(template_token) {
template_token.expand = expand_template_Colored_link;
}
// --------------------------------------------------------------------------------------------
// 一些會添加 anchors 的特殊模板。
// {{Anchor|anchor|別名1|別名2}}
function expand_template_Anchor(options) {
var parameters = this.parameters;
var wikitext = [];
for (/* let */var index = 1; index < this.length; index++) {
var anchor = parameters[index];
if (!anchor) {
continue;
}
if (typeof anchor !== 'string') {
// e.g., `{{Anchor|{{u|Emojibot}}}}` @ zhwiki
library_namespace.warn('expand_template_Anchor: 特殊 anchor: #'
+ anchor);
// console.trace(anchor);
// old jawiki {{Anchor}}
// e.g., [[終着駅シリーズ]]: {{Anchor|[[牛尾正直]]}}
// {{Anchor|A[[B|C]]}} → "AC"
anchor = wiki_API.wikitext_to_plain_text(anchor.toString());
}
// 多空格、斷行會被轉成單一 " "。
anchor = anchor.replace(/[\s\n]{2,}/g, ' ');
// class="anchor"
wikitext.push('<span id="' + anchor + '"></span>');
}
return wikitext.join('');
}
function parse_template_Anchor(template_token, index, parent, options) {
template_token.expand = expand_template_Anchor;
}
// --------------------------------------------------------------------------------------------
function expand_template_Visible_anchor(options) {
var parameters = this.parameters;
// {{Visible anchor}}(別名{{Vanc}}は似たテンプレートですが、1個目のリンク先が表示されます。
return expand_template_Anchor.call(this, options)
// + '<span class="vanchor-text">'
+ (parameters.text || parameters[1] || '')
// + '</span>'
;
}
function parse_template_Visible_anchor(template_token, index, parent,
options) {
template_token.expand = expand_template_Visible_anchor;
}
// --------------------------------------------------------------------------------------------
function expand_template_Term(options) {
var parameters = this.parameters;
var wikitext = '<dt id="'
+ wiki_API.wikitext_to_plain_text(
//
parameters.id || parameters.term || parameters[1] || '')
.replace(/"/g, '').toLowerCase()
+ '">'
+ (parameters.content || parameters[2] || parameters.term
|| parameters[1] || '') + '</dt>';
// console.log(wikitext);
return wikitext;
}
function parse_template_Term(template_token, index, parent, options) {
template_token.expand = expand_template_Term;
}
// --------------------------------------------------------------------------------------------
function expand_template_Wikicite(options) {
var parameters = this.parameters;
// class="citation wikicite"
var wikitext = '<cite id='
+ (parameters.ref || parameters.id
&& ('"Reference-' + parameters.id + '"')) + '>'
+ parameters.reference + '</cite>';
// console.log(wikitext);
return wikitext;
}
function parse_template_Wikicite(template_token, index, parent, options) {
template_token.expand = expand_template_Wikicite;
}
// --------------------------------------------------------------------------------------------
function expand_template_SfnRef(options) {
var parameters = this.parameters;
var anchor = 'CITEREF';
for (var index = 1; index <= 5 && parameters[index]; index++) {
anchor += trim_param(parameters[index]);
}
// TODO: test year
// anchor = anchor.replace(/\s+/g, ' ');
return anchor;
}
function parse_template_SfnRef(template_token, index, parent, options) {
template_token.expand = expand_template_SfnRef;
}
// --------------------------------------------------------------------------------------------
// @see local function sfn (frame) @
// https://en.wikipedia.org/wiki/Module:Footnotes
function expand_template_Sfn(options) {
var parameters = this.parameters;
var anchor = 'cite_ref-FOOTNOTE';
for (var index = 1; index <= 5 && parameters[index]; index++) {
anchor += trim_param(parameters[index]);
}
if (parameters.p)
anchor += trim_param(parameters.p);
if (parameters.pp)
anchor += trim_param(parameters.pp);
if (parameters.loc)
anchor += trim_param(parameters.loc);
anchor = anchor.replace(/\s+/g, ' ');
var wikitext = [];
var reference_index = 1;
var pointer_index = 0;
// TODO: 這個數值必須按照 reference_index 遞增。
var ref_anchor = anchor + '-' + reference_index;
wikitext.push('<ref name="' + ref_anchor + '">'
// TODO: + content
+ '</ref>',
//
'<a id="' + anchor + '_' + reference_index + '-'
// TODO: 這個數值必須按照 pointer_index 遞增。
+ pointer_index + '" href="#' + ref_anchor + '">'
// TODO: 這個數值必須按照 reference_index 遞增。
+ '[' + reference_index + ']' + '</a>');
return wikitext.join('');
}
function parse_template_Sfn(template_token, index, parent, options) {
template_token.expand = expand_template_Sfn;
}
// --------------------------------------------------------------------------------------------
// @see createEpisodeNumberCellSecondary() @ [[Module:Episode list]]
var EpisodeNumbers = [ 'EpisodeNumber', 'EpisodeNumber2', 'EpisodeNumber3' ];
function expand_template_Episode_list(options) {
// console.trace(this);
var parameters = this.parameters;
var anchor_prefix = this.anchor_prefix || '';
var wikitext = [];
for (var index = 0; index < EpisodeNumbers.length; index++) {
var anchor = trim_param(parameters[EpisodeNumbers[index]]);
// console.trace([ EpisodeNumbers[index], anchor ]);
// @see getEpisodeText() @ [[Module:Episode list]]
var matched = anchor && anchor.match(/^\w+/);
if (matched) {
anchor = matched[0];
// 極度簡化版。
wikitext.push('<th id="' + anchor_prefix + 'ep' + anchor
+ '"></th>');
}
}
// @see createProductionCodeCell() @ [[Module:Episode list]]
var anchor = trim_param(parameters.ProdCode);
if (anchor) {
wikitext.push('<td id="' + 'pc' + wiki_API.plain_text(anchor)
+ '"></td>');
}
// console.trace(wikitext);
return wikitext.join('');
}
function parse_template_Episode_list(template_token, index, parent, options) {
template_token.expand = expand_template_Episode_list;
}
function expand_template_Episode_table(options) {
}
function parse_template_Episode_table(template_token, index, parent,
options) {
// template_token.expand = expand_template_Episode_table;
var parameters = template_token.parameters;
var episodes = parameters.episodes;
var anchor_prefix = trim_param(parameters.anchor);
// console.trace(anchor_prefix);
if (anchor_prefix && episodes) {
var session = wiki_API.session_of_options(options) || wiki_API;
wiki_API.parser.parser_prototype.each.call(episodes,
//
'transclusion', function(template_token) {
if (session.is_template(template_token, [ 'Episode list',
'Episode list/sublist' ])) {
template_token.anchor_prefix = anchor_prefix;
}
}, options);
}
}
function expand_template_Episode_table__part(options) {
var parameters = this.parameters;
// console.trace(parameters);
// [[Module:Episode table]]
var id = trim_param(parameters.id);
if (!id) {
// partTypes
[ 'Act', 'Chapter', 'Part', 'Volume', 'Week' ].forEach(function(
prefix) {
var value = parameters[prefix.toLowerCase()];
if (value)
id = prefix + ' ' + value;
});
if (parameters.subtitle) {
id = (id ? id + ': ' : '') + parameters.subtitle;
}
// console.trace(id);
}
if (id) {
return '<td id="' + wiki_API.plain_text(id) + '"></td>';
}
}
function parse_template_Episode_table__part(template_token, index, parent,
options) {
template_token.expand = expand_template_Episode_table__part;
}
// --------------------------------------------------------------------------------------------
function parse_template_Pin_message(template_token, index, parent, options) {
var parameters = template_token.parameters, message_expire_date;
if (parameters[1]) {
options = library_namespace.new_options(options);
options.get_timevalue = true;
message_expire_date = wiki_API.parse.date(parameters[1], {
get_timevalue : true,
});
}
// console.trace([ message_expire_date, parameters ]);
template_token.message_expire_date = message_expire_date || Infinity;
}
// --------------------------------------------------------------------------------------------
// 有缺陷的簡易型 Lua patterns to JavaScript RegExp
function Lua_pattern_to_RegExp_pattern(pattern) {
return String(pattern).replace(/%l/g, 'a-z').replace(/%u/g, 'A-Z')
// e.g., %d, %s, %S, %w, %W
.replace(/%/g, '\\');
}
// https://en.wikipedia.org/wiki/Module:Check_for_unknown_parameters
function check_template_for_unknown_parameters(template_token, options) {
var valid_parameters = this.valid_parameters, valid_RegExp_parameters = this.valid_RegExp_parameters;
var invalid_parameters = Object.keys(template_token.parameters)
//
.filter(function() {
if (valid_parameters.has(parameter))
return;
return !valid_RegExp_parameters.some(function(pattern) {
return pattern.test(parameter);
});
}, this);
if (invalid_parameters.length === 0) {
return;
}
var return_value = {
invalid_parameters : invalid_parameters
};
var unknown_text = this.parameters.unknown || 'Found _VALUE_, ';
var preview_text = this.parameters.preview;
unknown_text = invalid_parameters.map(function(parameter) {
return unknown_text.replace(/_VALUE_/g, parameter);
}).join('').replace(/[,\s]+$/, '');
if (preview_text) {
preview_text = invalid_parameters.map(function(parameter) {
return preview_text.replace(/_VALUE_/g, parameter);
}).join('').replace(/[,\s]+$/, '');
}
return {
invalid_parameters : invalid_parameters,
preview_text : preview_text || unknown_text,
unknown_text : unknown_text
};
}
function parse_module_Check_for_unknown_parameters(token, index, parent,
options) {
var parameters = token.parameters, valid_parameters = token.valid_parameters = new Set, valid_RegExp_parameters = token.valid_RegExp_parameters = [];
for (var index = 1; index < token.length; index++) {
var value = parameters[index];
if (value)
valid_parameters.add(String(value));
if (value = parameters['regexp' + index]) {
try {
value = new RegExp('^'
+ Lua_pattern_to_RegExp_pattern(value) + '$');
valid_RegExp_parameters.push(value);
} catch (e) {
library_namespace.error([
//
'parse_module_Check_for_unknown_parameters: ', {
T : [
// gettext_config:{"id":"cannot-convert-lua-pattern-to-regexp-pattern-$1"}
'Cannot convert Lua pattern to RegExp pattern: %1',
//
value ]
} ]);
}
}
}
token.check_template = check_template_for_unknown_parameters
.bind(token);
}
// --------------------------------------------------------------------------------------------
function expand_module_IPAddress(options) {
// console.trace(this);
var parameters = this.parameters;
// console.trace(parameters);
if (this.function_name === 'isIp') {
// [ , 'IPAddress', 'isIp', '...' ]
var is_IP = library_namespace.is_IP(parameters[1]);
return is_IP ? String(is_IP) : '';
}
// TODO:
}
function parse_module_IPAddress(token, index, parent, options) {
token.expand = expand_module_IPAddress;
}
// --------------------------------------------------------------------------------------------
function expand_template_Template_link(options) {
var parameters = this.parameters;
return '&#123;&#123;[[Template:' + parameters[1] + '|' + parameters[1]
+ ']]&#125;&#125;';
}
function parse_template_Template_link(token, index, parent, options) {
token.expand = expand_template_Template_link;
}
// --------------------------------------------------------------------------------------------
// export 導出.
// general_functions 必須在個別 wiki profiles 之前載入。
// 如 CeL.application.net.wiki.template_functions.jawiki 依賴於
// general_functions
wiki_API.template_functions.functions_of_all_sites = {
Void : parse_template_Void,
Center : parse_template_Center,
// 一些會用於章節標題的特殊模板。 for preprocess_section_link_token()
Font : parse_template_Font,
Color : parse_template_Color,
'Colored link' : parse_template_Colored_link,
'@' : parse_template_At,
'User link' : parse_template_User_link,
'Module:If empty' : parse_module_If_empty,
// 一些會添加 anchors 的特殊模板。
// 會生成網頁錨點的模板或模組。
// Templates or modules that generate web anchors
Anchor : parse_template_Anchor,
'Module:Anchor' : parse_template_Anchor,
'Visible anchor' : parse_template_Visible_anchor,
Term : parse_template_Term,
Wikicite : parse_template_Wikicite,
// Sfn : parse_template_Sfn,
SfnRef : parse_template_SfnRef,
'Episode table' : parse_template_Episode_table,
'Episode table/part' : parse_template_Episode_table__part,
'Episode list' : parse_template_Episode_list,
'Episode list/sublist' : parse_template_Episode_list,
// wiki/routine/20210429.Auto-archiver.js: avoid being archived
'Pin message' : parse_template_Pin_message,
'Module:Check for unknown parameters' : parse_module_Check_for_unknown_parameters,
'Module:IPAddress' : parse_module_IPAddress,
// TODO
// 'Module:Unsubst' : parse_module_Unsubst,
// 'Template link'
Tl : parse_template_Template_link
};
// --------------------------------------------------------------------------------------------
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}

View File

@@ -0,0 +1,161 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): 常用模板特設功能。本工具檔放置的是指定 wiki
* 計畫特有的模板。
*
* 注意: 本程式庫必須應各 wiki project 模板內容改動而改寫。
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2021/1/24 16:6:50
*/
// 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.template_functions.jawiki',
require : 'data.native.'
// Should also load essential MediaWiki modules
+ '|application.net.wiki.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki;
// @inner
// var is_api_and_title = wiki_API.is_api_and_title,
// normalize_title_parameter = wiki_API.normalize_title_parameter;
var to_exit = wiki_API.parser.parser_prototype.each.exit;
// e.g., 'zhwiki'
var module_site_name = this.id.match(/[^.]+$/)[0];
function empty_string(/* options */) {
// var token = this;
return '';
}
// --------------------------------------------------------------------------------------------
// token.expand() 可將模板轉換成一般 wiki 語法。
// https://www.mediawiki.org/w/api.php?action=help&modules=expandtemplates
// 用於 function preprocess_section_link_token()。
// --------------------------------------------------------------------------------------------
function expand_template_Enlink(options) {
var parameters = this.parameters;
var lang = parameters[3] || 'en';
var wikitext;
if (parameters.a === 'on') {
wikitext = lang;
} else {
wikitext = (parameters[3] ? parameters[3] + ':' : '')
+ (parameters[2] || parameters[1]);
if (parameters.i === 'on')
wikitext = "''" + wikitext + "''";
}
wikitext = '[[:' + lang + ':' + parameters[1]
//
+ '|' + wikitext + ']]';
if (!parameters.p || parameters.p === 'on')
wikitext = '&nbsp;(' + wikitext + '&nbsp;';
if (!parameters.s || parameters.s === 'on')
wikitext = '<small>' + wikitext + '</small>';
return ' (' + parameters[1] + ')';
}
function parse_template_Enlink(token) {
token.expand = expand_template_Enlink;
}
// --------------------------------------------------------------------------------------------
function expand_template_to_display_language(options) {
// console.trace(this.toString());
var parameters = this.parameters;
return parameters[1];
}
function parse_template_to_display_language(token) {
token.expand = expand_template_to_display_language;
}
// --------------------------------------------------------------------------------------------
function expand_template_拡張漢字(options) {
var parameters = this.parameters;
return parameters[2] || parameters[1];
}
function parse_template_拡張漢字(token) {
token.expand = expand_template_拡張漢字;
}
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors().
// 転送先のアンカーはTemplate:RFDの中に納まっている
// e.g., {{RFD notice
// |'''対象リダイレクト:'''[[Wikipedia:リダイレクトの削除依頼/受付#RFD長崎市電|長崎市電(受付依頼)]]|...}}
function expand_template_RFD(options) {
var parameters = this.parameters;
// {{RFD|リダイレクト元ページ名|リダイレクト先ページ名}}
return '<span id="RFD' + parameters[1] + '"></span>'
// TODO: + ...
;
}
function parse_template_RFD(token) {
token.expand = expand_template_RFD;
}
// --------------------------------------------------------------------------------------------
// export 導出.
wiki_API.template_functions.functions_of_site[module_site_name] = {
// 一些會添加 anchors 的特殊模板。
Anchors : wiki_API.template_functions.functions_of_all_sites.Anchor,
Enlink : parse_template_Enlink,
ARIB外字フォント : parse_template_to_display_language,
CP932フォント : parse_template_to_display_language,
JIS90フォント : parse_template_to_display_language,
JIS2004フォント : parse_template_to_display_language,
MacJapanese : parse_template_to_display_language,
変体仮名フォント : parse_template_to_display_language,
絵文字フォント : parse_template_to_display_language,
補助漢字フォント : parse_template_to_display_language,
通貨フォント : parse_template_to_display_language,
拡張漢字 : parse_template_拡張漢字,
RFD : parse_template_RFD
};
// --------------------------------------------------------------------------------------------
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}

View File

@@ -0,0 +1,210 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): 常用模板特設功能。本工具檔放置的是指定 wiki
* 計畫特有的模板。
*
* 注意: 本程式庫必須應各 wiki project 模板內容改動而改寫。
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2021/1/24 16:6:50
*/
// 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.template_functions.zhmoegirl',
require : 'data.native.'
// Should also load essential MediaWiki modules
+ '|application.net.wiki.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki;
// @inner
// var is_api_and_title = wiki_API.is_api_and_title,
// normalize_title_parameter = wiki_API.normalize_title_parameter;
var to_exit = wiki_API.parser.parser_prototype.each.exit;
// e.g., 'zhwiki'
var module_site_name = this.id.match(/[^.]+$/)[0];
function empty_string(/* options */) {
// var token = this;
return '';
}
// --------------------------------------------------------------------------------------------
// token.expand() 可將模板轉換成一般 wiki 語法。
// https://www.mediawiki.org/w/api.php?action=help&modules=expandtemplates
// 用於 function preprocess_section_link_token()。
// --------------------------------------------------------------------------------------------
// for get_all_anchors()
function expand_template_A(options) {
var parameters = this.parameters;
// {{a|锚点名称|显示文字}}
return '<span id="' + parameters[1] + '">'
+ (parameters[2] || parameters[1]) + '</span>';
}
function parse_template_A(token, index, parent, options) {
token.expand = expand_template_A;
}
// --------------------------------------------------------------------------------------------
// [[Module:Ruby]]
function expand_module_Ruby(parameters) {
// converted wikitext
var wikitext = [];
wikitext.push('<ruby'
+ (parameters.id ? ' id="' + parameters.id + '"' : '') + '>');
wikitext.push('<rb'
+ (parameters.rbid ? ' id="' + parameters.rbid + '"' : '')
+ '>' + (parameters[1] || '') + '</rb>');
wikitext.push('');
wikitext.push('<rt'
+ (parameters.rtid ? ' id="' + parameters.rtid + '"' : '')
+ '>' + (parameters[2] || '') + '</rt>');
wikitext.push('');
wikitext.push('</ruby>');
return wikitext.join('');
}
// for get_all_anchors()
function expand_template_Ruby(options) {
var parameters = this.parameters;
// {{Ruby|文字|注音|文字的語言标签|注音的語言标签}}
return expand_module_Ruby(parameters);
}
function parse_template_Ruby(token, index, parent, options) {
token.expand = expand_template_Ruby;
}
// --------------------------------------------------------------------------------------------
// for preprocess_section_link_token()
function expand_template_Dead(options) {
var parameters = this.parameters;
return parameters[1];
}
function parse_template_Dead(token) {
token.expand = expand_template_Dead;
}
// --------------------------------------------------------------------------------------------
// for preprocess_section_link_token()
function expand_template_黑幕(options) {
var parameters = this.parameters;
return parameters[1];
}
function parse_template_黑幕(token) {
token.expand = expand_template_黑幕;
}
// --------------------------------------------------------------------------------------------
// for preprocess_section_link_token()
// {{Lj|...}} 是日語{{lang|ja|...}}的縮寫 @ zh.moegirl
function expand_template_Lj(options) {
var parameters = this.parameters;
return '-{' + parameters[1] + '}-';
}
function parse_template_Lj(token) {
token.expand = expand_template_Lj;
}
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors() @ [[ACGN作品中出場的鐵路車站列表]]
function expand_template_铁路车站名(options) {
var parameters = this.parameters;
return '<span id="' + (parameters.name || parameters[1]) + '">'
// TODO: The content is skipped.
+ '</span>';
}
function parse_template_铁路车站名(token) {
token.expand = expand_template_铁路车站名;
}
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors() as section title
// @ [[ARGONAVIS from BanG Dream! 翻唱曲列表]]
function expand_template_ARGONAVIS_Icon(options) {
// TODO: The content is skipped.
return '';
}
function parse_template_ARGONAVIS_Icon(token) {
token.expand = expand_template_ARGONAVIS_Icon;
}
// --------------------------------------------------------------------------------------------
// Not completed! Only for get_all_anchors()
// @ zh.moegirl [[FLOWERS(Innocent Grey)]]
function expand_template_Gradient_Text(options) {
var parameters = this.parameters;
// {{Gradient_Text|漸變色代碼|文字內容|title=鼠標懸停在文字上顯示的注釋}}
return parameters[2] || '';
}
function parse_template_Gradient_Text(token) {
token.expand = expand_template_Gradient_Text;
}
// --------------------------------------------------------------------------------------------
// export 導出.
wiki_API.template_functions.functions_of_site[module_site_name] = {
// 一些會添加 anchors 的特殊模板。
A : parse_template_A,
Ruby : parse_template_Ruby,
铁路车站名 : parse_template_铁路车站名,
// 一些會用於章節標題的特殊模板。 for preprocess_section_link_token()
Dead : parse_template_Dead,
黑幕 : parse_template_黑幕,
Lj : parse_template_Lj,
'ARGONAVIS/Icon' : parse_template_ARGONAVIS_Icon,
'Gradient Text' : parse_template_Gradient_Text
};
// library_namespace.info(module_site_name + ': 採用 zhwiki 的模板特設功能設定。');
wiki_API.template_functions.functions_of_site[module_site_name][wiki_API.template_functions.KEY_dependent_on] = [ 'zhwiki' ];
// --------------------------------------------------------------------------------------------
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}

View File

@@ -0,0 +1,387 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): 常用模板特設功能。本工具檔放置的是指定 wiki
* 計畫特有的模板。
*
* 注意: 本程式庫必須應各 wiki project 模板內容改動而改寫。
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2021/1/24 16:6:50
*/
// More examples: see /_test suite/test.js
// Wikipedia bots demo: https://github.com/kanasimi/wikibot
'use strict';
// 'use asm';
// @examples
(function() {
require('./wiki loader.js');
CeL.run('application.net.wiki.template_functions');
var wiki = Wiki(true, 'zh');
wiki.page('簡繁轉換一對多列表').parse(function(parsed) {
// var page_data = parsed.page;
parsed.each('Template:簡繁轉換', function(token) {
console.log(token. + '⇄' + token.);
});
});
});
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
// module name
name : 'application.net.wiki.template_functions.zhwiki',
require : 'data.native.'
// Should also load essential MediaWiki modules
+ '|application.net.wiki.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki;
// @inner
// var is_api_and_title = wiki_API.is_api_and_title,
// normalize_title_parameter = wiki_API.normalize_title_parameter;
var to_exit = wiki_API.parser.parser_prototype.each.exit;
// e.g., 'zhwiki'
var module_site_name = this.id.match(/[^.]+$/)[0];
function empty_string(/* options */) {
// var token = this;
return '';
}
// --------------------------------------------------------------------------------------------
// token.expand() 可將模板轉換成一般 wiki 語法。
// https://www.mediawiki.org/w/api.php?action=help&modules=expandtemplates
// 用於 function preprocess_section_link_token()。
// --------------------------------------------------------------------------------------------
function expand_template_A(options) {
var parameters = this.parameters;
return (parameters.name ? '<span id="' + parameters.name
//
+ '"></span>' : '') + '[[' + parameters[1]
//
+ (parameters[2] ? '|' + parameters[2] : '') + ']]';
}
// --------------------------------------------------------------------------------------------
// [[w:zh:Template:Al]]
function expand_template_Al(options) {
var token = this;
return token.page_title_list.map(function(title) {
return wiki_API.title_link_of(title);
}).join('、');
}
function parse_template_Al(token, index, parent, options) {
var index = 0, page_title_list = [];
while (index < token.length) {
var page_title = token.parameters[++index];
// allow `{{al||title}}`
if (page_title)
page_title_list.push(page_title);
}
Object.assign(token, {
page_title_list : page_title_list,
expand : expand_template_Al
});
return page_title_list;
}
// --------------------------------------------------------------------------------------------
function parse_template_不存檔(token, index, parent, options) {
token.message_expire_date = Infinity;
}
// --------------------------------------------------------------------------------------------
function expand_template_楷體(options) {
var parameters = this.parameters;
return '<span class="template-kai">' + (parameters[1] || '楷体')
+ '</span>';
}
function parse_template_楷體(token, index, parent, options) {
token.expand = expand_template_楷體;
}
// --------------------------------------------------------------------------------------------
/**
* [[Template:Interlanguage link]] 跨語言模板 多語言模板。會為 token 增加下列屬性: <code>
</code>
*/
var interlanguage_link_template_attributes = {
// local_title: local title 中文條目名
"local_page_title" : '',
// 只會提供第一個。
"foreign_language_code" : '',
// 只會提供第一個。
"foreign_page_title" : '',
"foreign_page_mapper" : {
// foreign_language: foreign language code 外文語言代號
// foreign_title: foreign title 外文條目名
foreign_language_code : 'foreign_page_title'
},
// label: label text displayed 顯示名
"display_text" : '',
// Keep foreign language links when displayed
"preserve_foreign_links" : true,
"wikidata_entity_id" : '' || 1,
// 屬性的index改變屬性值時使用。
"attribute_index" : {
"local_page_title" : 1,
// 只會提供第一個。
"foreign_language_code" : 1,
// 只會提供第一個。
"foreign_page_title" : 1,
"display_text" : 1,
"preserve_foreign_links" : 1,
"wikidata_entity_id" : 1
}
};
function setup_interlanguage_link_template_parameters(template_pattern) {
var parsed_token = wiki_API.parse(template_pattern);
var attribute_index = Object.create(null);
var configuration = {
attribute_index : attribute_index
};
var parameters = parsed_token.parameters;
for ( var parameter in parameters) {
var attribute_name = parameters[parameter];
if (attribute_name in interlanguage_link_template_attributes)
attribute_index[attribute_name] = parameter;
}
var functions_of_site = wiki_API.template_functions.functions_of_site[module_site_name];
if (functions_of_site[parsed_token.name]) {
library_namespace
.error('setup_interlanguage_link_template_parameters: '
+ '已設定' + parsed_token.name
+ '之模板特設功能,無法設定跨語言模板功能。');
return;
}
functions_of_site[parsed_token.name] = parse_interlanguage_link_template
.bind(configuration);
}
function parse_interlanguage_link_template(token, index, parent, options) {
var configuration = this;
var attribute_index = configuration.attribute_index;
var foreign_page_mapper = Object.create(null);
for ( var attribute_name in attribute_index) {
if (attribute_index[attribute_name] in token.parameters)
token[attribute_name] = token.parameters[attribute_index[attribute_name]];
}
if ('foreign_language_code' in token)
foreign_page_mapper[token.foreign_language_code] = token.foreign_page_title;
token.attribute_index = attribute_index;
token.foreign_page_mapper = foreign_page_mapper;
}
// --------------------------------------------------------------------------------------------
// {{Lang|ja|參數值}} → -{參數值}-
function expand_template_Lang(options) {
var parameters = this.parameters;
return /^(?:zh|gan)/.test(parameters[1]) ? parameters[2] : '-{'
+ parameters[2] + '}-';
}
function parse_template_Lang(token, index, parent, options) {
token.expand = expand_template_Lang;
}
// --------------------------------------------------------------------------------------------
// [[w:zh:Template:NoteTA]]
function parse_template_NoteTA(token, options) {
var conversion_list = Object.assign([], {
// 固定轉換規則
// fixed : [],
// 公共轉換組
group_data : [],
groups : []
});
var index, value = token.parameters.T;
if (value) {
// 標題轉換
conversion_list.title = value;
}
// TODO: {{NoteTA}} 使用「1=」可以同時轉換標題和正文(T=)
for (index = 1; index < token.length; index++) {
value = token.parameters[index];
if (!value)
continue;
// [[w:zh:模組:NoteTA]]
// @see function item_to_conversion(item) @
// CeL.application.net.wiki
value = wiki_API.parse('-{A|' + value + '}-', {
normalize : true,
with_properties : true
});
if (typeof value === 'string') {
// 遇到無法轉換的值別 throw。 e.g., "a\nb"
continue;
}
// value.parameter_name = index;
value.index = token.index_of[index];
// console.log(value);
conversion_list.push(value);
}
// [[w:zh:Module:NoteTA]]
for (index = 1; index < token.length; index++) {
var parameter_name = 'G' + index;
value = token.parameters[parameter_name];
if (!value)
continue;
value = wiki_API.parse.wiki_token_to_key(value);
// console.trace(value);
if (typeof value === 'string') {
value = value.replace(/_/g, ' ').trim();
} else {
library_namespace.warn('parse_template_NoteTA: 非字串之公共轉換組名稱: ['
+ value + '] @ ' + token);
console.trace(value);
}
conversion_list.groups.push(value.toString());
conversion_list.group_data[value.toString()] = {
parameter_name : parameter_name,
group_name : value,
index : token.index_of[parameter_name]
};
// TODO
}
Object.assign(token, {
conversion_list : conversion_list,
expand : empty_string
});
return conversion_list;
}
// --------------------------------------------------------------------------------------------
function template_簡繁轉換_to_string(template_token, parameter) {
var words = template_token.parameters[parameter];
if (Array.isArray(words)) {
words = words.map(function(token) {
if (typeof token === 'string')
return token;
if (token.tag === 'sup') {
// e.g., "<sup>台/陸繁</sup>"
return '';
}
if (token.type === 'transclusion') {
if (token.name === 'Lang'
//
&& typeof token.parameters[2] === 'string')
return token.parameters[2];
if (token.name === '僻字') {
// console.log(token.toString());
}
if (token.name === '僻字'
//
&& typeof token.parameters[1] === 'string')
return token.parameters[1];
}
throw new Error('包含無法處理的字元: ' + token);
}).join('');
}
words = library_namespace.HTML_to_Unicode(words);
// [[w:zh:Unicode字符平面映射]]
// http://ubuntu-rubyonrails.blogspot.com/2009/06/unicode.html
words = words.replace(
// 發音用 Pinyin diacritic-vowel combinations:
// \u00E0-\u00FC [[w:en:Latin-1 Supplement (Unicode block)]]
// \u0100-\u017F [[w:en:Latin Extended-A]]
// \u01CD-\u01DC [[w:en:Latin Extended-B]]
/[、a-z\u00E0-\u00FC\u0100-\u017F\u01CD-\u01DC\uD800-\uDFFF]/g, '');
if (false && /[^\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u2E80-\u2EFF]/
.test(words)) {
// words.charCodeAt(0).toString(16)
console.log([ words, words.replace(
// 匹配中文字符的正則表達式
/[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u2E80-\u2EFF]/g,
//
'') ]);
// throw words;
}
return words;
}
// for {{簡繁轉換}} @ [[w:zh:簡繁轉換一對多列表]]
// @see wiki_API.convert_Chinese()
function parse_template_簡繁轉換(token) {
Object.assign(token, {
: template_簡繁轉換_to_string(token, 's'),
: template_簡繁轉換_to_string(token, 't')
});
}
// --------------------------------------------------------------------------------------------
// export 導出.
wiki_API.template_functions.functions_of_site[module_site_name] = {
// 一些會用於章節標題的特殊模板。 for preprocess_section_link_token()
A : {
properties : {
expand : expand_template_A
}
},
Al : parse_template_Al,
// {{Do not archive}}
// wiki/routine/20210429.Auto-archiver.js: avoid being archived
不存檔 : parse_template_不存檔,
// [[Template:Interlanguage link]] 跨語言模板 多語言模板。
Lang : parse_template_Lang,
NoteTA : parse_template_NoteTA,
簡繁轉換 : parse_template_簡繁轉換
};
[ '{{Interlanguage link multi|local_page_title|foreign_language_code|foreign_page_title|lt=display_text|WD=wikidata_entity_id}}' ]
.forEach(setup_interlanguage_link_template_parameters);
// --------------------------------------------------------------------------------------------
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}

View File

@@ -0,0 +1,85 @@
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): 常用模板特設功能。本工具檔放置的是指定 wiki
* 計畫特有的模板。
*
* 注意: 本程式庫必須應各 wiki project 模板內容改動而改寫。
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
</code>
*
* @since 2021/11/19 5:4:8
*/
// 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.template_functions.zhwiktionary',
require : 'data.native.'
// Should also load essential MediaWiki modules
+ '|application.net.wiki.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki;
// @inner
// var is_api_and_title = wiki_API.is_api_and_title,
// normalize_title_parameter = wiki_API.normalize_title_parameter;
var to_exit = wiki_API.parser.parser_prototype.each.exit;
// e.g., 'zhwiktionary'
var module_site_name = this.id.match(/[^.]+$/)[0];
function empty_string(/* options */) {
// var token = this;
return '';
}
// --------------------------------------------------------------------------------------------
// token.expand() 可將模板轉換成一般 wiki 語法。
// https://www.mediawiki.org/w/api.php?action=help&modules=expandtemplates
// 用於 function preprocess_section_link_token()。
// --------------------------------------------------------------------------------------------
function expand_template_語言標題(options) {
var parameters = this.parameters;
return '\n==' + (parameters.l || parameters. || parameters.) + '==';
}
function parse_template_語言標題(token, index, parent, options) {
token.expand = expand_template_語言標題;
}
// --------------------------------------------------------------------------------------------
// export 導出.
wiki_API.template_functions.functions_of_site[module_site_name] = {
語言標題 : parse_template_語言標題,
};
// --------------------------------------------------------------------------------------------
// 不設定(hook)本 module 之 namespace僅執行 module code。
return library_namespace.env.not_to_extend_keyword;
}