/** * @name Wikiapi.js * * @fileoverview Main codes of module wikiapi (class Wikiapi) */ 'use strict'; /** * @description CeJS controller * * @type Function * @inner * * @see https://github.com/kanasimi/CeJS */ let CeL; try { // Load CeJS library. CeL = require('cejs'); if (typeof CeL.then === 'function' && typeof window === "object" && window.CeL) { // assert: @Snowpack CeL = window.CeL; } } catch (e) /* istanbul ignore next: Only for debugging locally */ { // https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md // const Wikiapi = require('./wikiapi.js'); require('./_CeL.loader.nodejs.js'); CeL = globalThis.CeL; } // assert: typeof CeL === 'function' // Load modules. // @see `wiki loader.js`: // https://github.com/kanasimi/wikibot/blob/master/wiki%20loader.js CeL.run(['interact.DOM', 'application.debug', // 載入不同地區語言的功能 for wiki.work()。 'application.locale', // 載入操作維基百科的主要功能。 'application.net.wiki', // Optional 可選功能 'application.net.wiki.data', 'application.net.wiki.admin', // Add color to console messages. 添加主控端報告的顏色。 'interact.console', // for 'application.platform.nodejs': CeL.env.arg_hash, wiki_API.cache(), // CeL.fs_mkdir(), wiki_API.read_dump() 'application.storage']); // -------------------------------------------------------- /** * @description syntactic sugar for CeJS MediaWiki module. CeL.net.wiki === CeL.wiki * * @inner */ const wiki_API = CeL.net.wiki; /** * key to get {@link wiki_API} operator when using {@link wiki_API}. * * @type Symbol * * @inner */ const KEY_SESSION = wiki_API.KEY_SESSION; // Set default language. 改變預設之語言。 wiki_API.set_language('en'); /** * @description key to get {@link wiki_API} operator inside {@link Wikiapi}. * this[KEY_wiki_session] inside module code will get {@link wiki_API} operator. * * @type Symbol * * @inner */ const KEY_wiki_session = Symbol('wiki_API session'); // for debug // Wikiapi.KEY_wiki_session = KEY_wiki_session; /** * @description main Wikiapi operator 操作子. * * @param {String|Object} [API_URL] - language code or service endpoint of MediaWiki project.
* Input {Object} will be treat as options. * * @class */ function Wikiapi(API_URL) { const wiki_session = new wiki_API(null, null, API_URL); // this[KEY_wiki_session] = new wiki_API(null, null, API_URL); setup_wiki_session.call(this, wiki_session); } // -------------------------------------------------------- /** * @description Bind {@link wiki_API} instance to {@link Wikiapi} instance * * @param {wiki_API} wiki_session - wiki_API session * * @inner */ function setup_wiki_session(wiki_session) { Object.defineProperty(this, KEY_wiki_session, { value: wiki_session, writable: true, }); } /** * @alias login * @description login into the target MediaWiki API using the provided username and password. * For bots, see [[Special:BotPasswords]] on your wiki. * * @param {String} user_name - Account username. * @param {String} password - Account's password. * @param {String} [API_URL] - API URL of target wiki site. * * @returns {Promise} Promise object represents {String} login_name * * @example Login to wiki site method 1. // const wiki = new Wikiapi; const login_options = { user_name: '', password: '', API_URL: 'en', // Ror lingualibre only. @see https://github.com/kanasimi/wikibot/blob/master/wiki%20configuration.sample.js //data_API_URL: 'https://lingualibre.org/api.php', //SPARQL_API_URL: 'https://lingualibre.org/bigdata/namespace/wdq/sparql', // Calling in another domain origin: '*' }; await wiki.login(login_options); // * * @example Login to wiki site method 2. // const wiki = new Wikiapi; await wiki.login('user_name', 'password', 'en'); // * * @memberof Wikiapi.prototype */ function Wikiapi_login(user_name, password, API_URL) { let options; if (!password && !API_URL && CeL.is_Object(user_name)) { options = user_name; } else if (CeL.is_Object(API_URL)) { options = { ...API_URL, user_name, password }; } else { options = { user_name, password, API_URL }; } function Wikiapi_login_executor(resolve, reject) { const wiki_session = wiki_API.login({ preserve_password: true, ...options, API_URL: options.API_URL || this[KEY_wiki_session].API_URL, callback(login_name, error) { if (error) { reject(error); } else { resolve(login_name); } }, // task_configuration_page: 'page title', }); setup_wiki_session.call(this, wiki_session); } return new Promise(Wikiapi_login_executor.bind(this)); } // -------------------------------------------------------- /** * @description attributes of {Object} page_data, will setup by {@link set_page_data_attributes}. * * @type Object * * @inner */ const page_data_attributes = { /** * @description get {String}page content, maybe undefined. * 條目/頁面內容 = wiki_API.revision_content(revision) * * @type String */ wikitext: { get() { // console.trace(this); // console.log(wiki_API.content_of(this, 0)); return wiki_API.content_of(this, 0); } }, /** * @description get {Object}revisions * * @type Object */ revision: { value: function revision(revision_NO) { return wiki_API.content_of(this, revision_NO); } }, /** * @description get {Attay} parsed data of page_data * * @type Array */ parse: { value: function parse(options) { // this === page_data // options = { ...options, [KEY_SESSION]: this[KEY_wiki_session] }; options = Wikiapi.prototype.append_session_to_options.call(this, options); // using function parse_page(options) @ wiki_API return wiki_API.parser(this, options).parse(); // return {Array}parsed } }, }; /** * @description Bind {@link page_data_attributes} to page_data * * @param {Object} page_data - page data * @param {wiki_API} wiki - wiki_API session * * @returns {Promise} Promise object represents {Object} page's data * * @inner */ function set_page_data_attributes(page_data, wiki) { // `page_data` maybe non-object when error occurres. if (page_data) { page_data[KEY_wiki_session] = wiki; Object.defineProperties(page_data, page_data_attributes); } return page_data; } /** * @alias page * @description given a title, returns the page's data. * * @param {String} title - page title * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Object} page's data * * @example load page // // on Wikipedia... const wiki = new Wikiapi('en'); // ...or other MediaWiki websites //const wiki = new Wikiapi('https://awoiaf.westeros.org/api.php'); let page_data = await wiki.page('Universe', { // You may also set rvprop. //rvprop: 'ids|content|timestamp|user', }); console.log(page_data.wikitext); // * * @example Get multi revisions // const wiki = new Wikiapi; let page_data = await wiki.page('Universe', { // Get multi revisions revisions: 2 }); console.log(page_data.wikitext); // * * @example parse wiki page (The parser is more powerful than the example. Please refer to link of wikitext parser examples showing in "Features" section of README.md.) // // Usage with other language const zhwiki = new Wikiapi('zh'); await zhwiki.login('user', 'password'); let page_data = await zhwiki.page('Universe'); // `page_data.parse(options)` will startup the parser process, create page_data.parsed. After .parse(), we can use parsed.each(). const parsed = page_data.parse(); // See all type in wiki_toString @ https://github.com/kanasimi/CeJS/tree/master/application/net/wiki/parser/wikitext.js // List all template name. parsed.each('template', token => console.log(token.name)); // List all [[Template:Tl]] token. parsed.each('Template:Tl', token => console.log(token)); // * * @example Get information from Infobox template // const wiki = new Wikiapi('en'); const page_data = await wiki.page('JavaScript'); const parsed = page_data.parse(); let infobox; // Read Infobox templates, convert to JSON. parsed.each('template', template_token => { if (template_token.name.startsWith('Infobox')) { infobox = template_token.parameters; return parsed.each.exit; } }); for (const [key, value] of Object.entries(infobox)) infobox[key] = value.toString(); // print json of the infobox console.log(infobox); // * * @memberof Wikiapi.prototype */ function Wikiapi_page(title, options) { function Wikiapi_page_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; wiki.page(title, (page_data, error) => { if (error) { reject(error); } else { resolve(set_page_data_attributes(page_data, wiki)); } }, { // node.js v12.22.7: Cannot use "?." rvlimit: options && options.revisions, ...options }); } return new Promise(Wikiapi_page_executor.bind(this)); } // -------------------------------------------------------- /** * @alias tracking_revisions * @description tracking revisions to lookup what revision had added / removed to_search. * * @param {String} title - page title * @param {String} to_search - filter / text to search. to_search(diff, revision, old_revision): `diff` 為從舊的版本 `old_revision` 改成 `revision` 時的差異。 * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Object} newer_revision, * newer_revision.page: page_data * * @memberof Wikiapi.prototype */ function Wikiapi_tracking_revisions(title, to_search, options) { function Wikiapi_tracking_revisions_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; wiki.tracking_revisions(title, to_search, (revision, page_data, error) => { if (error) { reject(error); } else { if (!revision) revision = Object.create(null); revision.page = page_data; resolve(revision); } }, options); } return new Promise(Wikiapi_tracking_revisions_executor.bind(this)); } // -------------------------------------------------------- /** * @description Handle the result of MediaWiki API when executing edit operation. * * @param {Function} reject - reject function * @param {any} error - error object / message * @param {any} [result] - result of MediaWiki API * * @returns {Boolean} Return true if the edit operation failed. * * @inner */ function reject_edit_error(reject, error, result) { // skip_edit is not error if (!error // @see wiki_API_edit.check_data || Array.isArray(error) && error[0] === Wikiapi.skip_edit[0]) { return; } if (Array.isArray(error) && typeof error[1] === 'string') { // console.log('' + reject); // console.trace(error); error = error[1]; const error_object = new Error(error); error_object.from_string = error; error = error_object // console.log(error); } if (result && typeof error === 'object') error.result = result; reject(error); return true; } /** * @alias edit_page * @description edits content of target page.
* Note: for multiple pages, you should use {@link Wikiapi#for_each_page}.
* Note: The function will check sections of [[User talk:user name/Stop]] if somebody tells us needed to stop edit. See mechanism to stop operations. * * @param {String} title - page title * @param {String|Function} content - 'wikitext page content' || page_data => 'wikitext' * @param {Object} [options] - options to run this function. e.g., { summary: '', bot: 1, nocreate: 1, minor: 1 } * * @returns {Promise} Promise object represents {Object} result of MediaWiki API * * @example edit page: method 1: basic operation // const enwiki = new Wikiapi; await enwiki.login('bot name', 'password', 'en'); const SB_page_data = await enwiki.page('Wikipedia:Sandbox'); // You may do some operations on SB_page_data const parsed = SB_page_data.parse(); parsed.each('template', template_token => { // modify template token }); // and then edit it. ** You MUST call enwiki.page() before enwiki.edit()! ** await enwiki.edit(parsed.toString(), { bot: 1, minor: 1, nocreate: 1 }); // exmaple 2: append text in the tail of page content await enwiki.edit(page_data => { return page_data.wikitext + '\nTest edit using {{GitHub|kanasimi/wikiapi}}.'; }, { bot: 1 }); // exmaple 3: replace page content await enwiki.edit('Just replace by this wikitext', { bot: 1, minor: 1, nocreate: 1, summary: 'test edit' }); // exmaple 4: append a new section await enwiki.edit('section content', { section: 'new', sectiontitle: 'section title', nocreate : 1, summary: 'test edit', }); // * * @example edit page: method 2: modufy summary inside function // const enwiki = new Wikiapi; await enwiki.login('bot name', 'password', 'en'); await enwiki.edit_page('Wikipedia:Sandbox', function (page_data) { this.summary += ': You may set additional summary inside the function'; delete this.minor; return page_data.wikitext + '\nTest edit using {{GitHub|kanasimi/wikiapi}}.'; }, { bot: 1, nocreate: 1, minor: 1, redirects: 1, summary: 'test edit' }); // * * @memberof Wikiapi.prototype */ function Wikiapi_edit_page(title, content, options) { function Wikiapi_edit_page_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; // console.trace([title, content]); // console.trace(`Wikiapi_edit_page 1: ${wiki_API.title_link_of(title)}, ${wiki.actions.length} actions, ${wiki.running}/${wiki.thread_count}/${wiki.actions[wiki_API.KEY_waiting_callback_result_relying_on_this]}.`); // console.trace(title); // CeL.set_debug(6); if (title) { // console.trace(wiki); options = { ...options, error_with_symbol: true }; // 預防 page 本身是非法的頁面標題。當 session.page() 出錯時,將導致沒有 .last_page。 if (wiki_API.is_page_data(title)) options.task_page_data = title; // call wiki_API_prototype_method() @ CeL.application.net.wiki.list wiki.page(title, (page_data, error) => { // console.trace('Set .page_to_edit:'); // console.log([title, page_data, error]); // console.log(wiki.actions[0]); // 手動指定要編輯的頁面。避免多執行續打亂 wiki.last_page。 options.page_to_edit = page_data; }, options); } // console.trace(`Wikiapi_edit_page 2: ${wiki_API.title_link_of(title)}, ${wiki.actions.length} actions, ${wiki.running}/${wiki.thread_count}/${wiki.actions[wiki_API.KEY_waiting_callback_result_relying_on_this]}.`); // console.trace(wiki); // console.trace(wiki.last_page); // wiki.edit(page contents, options, callback) wiki.edit(typeof content === 'function' ? function (page_data) { return content.call(this, set_page_data_attributes(page_data, wiki)); } : content, options, (title, error, result) => { // console.trace('Wikiapi_edit_page: callbacked'); // console.log(title); // console.log(wiki.running); // CeL.set_debug(6); if (!reject_edit_error(reject, error, result)) { // console.log('Wikiapi_edit_page: resolve'); resolve(title); } // console.log('Wikiapi_edit_page: callback() return'); }); // console.trace(`Wikiapi_edit_page 3: ${wiki_API.title_link_of(title)}, ${wiki.actions.length} actions, ${wiki.running}/${wiki.thread_count}/${wiki.actions[wiki_API.KEY_waiting_callback_result_relying_on_this]}.`); } return new Promise(Wikiapi_edit_page_executor.bind(this)); } // return Wikiapi.skip_edit; as a symbol to skip this edit, do not generate // warning message. // 可以利用 ((return [ wiki_API.edit.cancel, 'reason' ];)) 來回傳 reason。 // ((return [ wiki_API.edit.cancel, 'skip' ];)) 來跳過 (skip) 本次編輯動作,不特別顯示或處理。 // 被 skip/pass 的話,連警告都不顯現,當作正常狀況。 /** * @description Return Wikiapi.skip_edit when we running edit function, but do not want to edit current page. * * @memberof Wikiapi */ Wikiapi.skip_edit = [wiki_API.edit.cancel, 'skip']; // -------------------------------------------------------- /** * @alias move_page * @description Move page move_from_title to move_to_title. * * @param {Object|String} move_from_title - move from title * @param {Object|String} move_to_title - move to title * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {String} result of MediaWiki API * * @example Move move_from_title to move_to_title. // await wiki.move_page(move_from_title, move_to_title, { reason, noredirect: true, movetalk: true }); // * * @memberof Wikiapi.prototype */ function Wikiapi_move_page(move_from_title, move_to_title, options) { function Wikiapi_move_page_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; // using wiki_API.prototype.move_page() wiki.move_page(move_from_title, move_to_title, options, (data, error) => { if (error) { /** * e.g., { code: 'articleexists', info: 'A page of that name already exists, or the name you have chosen is not valid. Please choose another name.', '*': '...' } e.g., { code: 'missingtitle', info: "The page you specified doesn't exist.", '*': '...' } */ reject(error); } else { /** * e.g., { from: 'from', to: 'to', reason: 'move', redirectcreated: '', moveoverredirect: '' } */ resolve(data); } }, options); } return new Promise(Wikiapi_move_page_executor.bind(this)); } /** * @alias move_to * @description Move to move_to_title. Must call {@link Wikiapi#page} first! * * @param {Object|String} move_to_title - move to title * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {String} result of MediaWiki API * * @example Move move_from_title to move_to_title. // page_data = await wiki.page(move_from_title); await wiki.move_to(move_to_title, { reason: reason, noredirect: true, movetalk: true }); // * * @memberof Wikiapi.prototype */ function Wikiapi_move_to(move_to_title, options) { function Wikiapi_move_to_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; if (!wiki.last_page) { reject(new Error(Wikiapi_move_to.name + ': Must call .page() first! ' // gettext_config:{"id":"cannot-move-to-$1"} + CeL.gettext('Cannot move to %1', wiki_API.title_link_of(move_to_title)))); return; } // using wiki_API.prototype.move_to() wiki.move_to(move_to_title, options, (data, error) => { if (error) { /** * e.g., { code: 'articleexists', info: 'A page of that name already exists, or the name you have chosen is not valid. Please choose another name.', '*': '...' } e.g., { code: 'missingtitle', info: "The page you specified doesn't exist.", '*': '...' } */ reject(error); } else { /** * e.g., { from: 'from', to: 'to', reason: 'move', redirectcreated: '', moveoverredirect: '' } */ resolve(data); } }, options); } return new Promise(Wikiapi_move_to_executor.bind(this)); } // -------------------------------------------------------- /** * @alias query * @description query MediaWiki API manually * * @param {Object} parameters - parameters to call MediaWiki API * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Object} result of MediaWiki API * * @example query flow-parsoid-utils // const wiki = new Wikiapi('mediawiki'); const results = await wiki.query({ action: "flow-parsoid-utils", content: "bold & italic", title: "MediaWiki", from: "html", to: "wikitext" }); // * * @memberof Wikiapi.prototype */ function Wikiapi_query(parameters, options) { function Wikiapi_query_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; wiki.query_API(parameters, (data, error) => { if (error) { reject(error); } else { resolve(data); } }, { post_data_only: true, ...options }); } return new Promise(Wikiapi_query_executor.bind(this)); } // -------------------------------------------------------- /** * @alias purge * @description Purge the cache for the given title. * * @param {Object} title - page title * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Object} page_data * * @example query flow-parsoid-utils // const metawiki = new Wikiapi('meta'); let page_data = await metawiki.purge('Project:Sandbox'); // * * @memberof Wikiapi.prototype */ function Wikiapi_purge(title, options) { if (CeL.is_Object(title) && !options) { // shift arguments. [title, options] = [null, title]; } function Wikiapi_purge_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; if (title) { wiki.page(title); } // using wiki_API.purge wiki.purge((data, error) => { if (error) { reject(error); } else { resolve(data); } }, options); } return new Promise(Wikiapi_purge_executor.bind(this)); } // -------------------------------------------------------- /** * @description Bind properties to {@link wiki_API} data entity. * 設定 wikidata entity object,讓我們能直接操作 entity.modify(),並且避免洩露 wiki_API session。 * * @param {Object} data_entity - wiki_API data entity * * @inner */ function setup_data_entity(data_entity) { if (!data_entity) return; // assert: data_entity[KEY_SESSION].host === this // console.trace(data_entity[KEY_SESSION].host === this); delete data_entity[KEY_SESSION]; Object.defineProperties(data_entity, { [KEY_wiki_session]: { value: this }, modify: { value: modify_data_entity }, }); } /** * @description Modify data entity * * @param {Object} data_entity - wiki_API data entity * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Object} result data entity * * @inner */ function modify_data_entity(data_to_modify, options) { function modify_data_entity_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; // console.trace(wiki); // using function wikidata_edit() @ // https://github.com/kanasimi/CeJS/blob/master/application/net/wiki/data.js // wiki.edit_data(id, data, options, callback) wiki.data(this).edit_data(data_to_modify || this, options, (data_entity, error) => { // console.trace([data_entity, error]); if (error) { reject(error); } else { setup_data_entity.call(wiki, data_entity); resolve(data_entity); } }); } return new Promise(modify_data_entity_executor.bind(this)); } /** * @alias data * @description Get wikidata entity / property * * @param {Object} data_entity - wiki_API data entity * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Object} wikidata entity / property * * @example Get wikidata entity method 1 // const wiki = new Wikiapi; const data_entity = await wiki.data('Q1'); // Work with other language console.assert(CeL.wiki.data.value_of(data_entity.labels.zh) === '宇宙'); // * * @example Get wikidata entity of [[Human]] // const wiki = new Wikiapi; const page_data = await wiki.page('Human'); const data_entity = await wiki.data(page_data); console.assert(CeL.wiki.data.value_of(data_entity.labels.zh) === '人類'); // * * @example Get wikidata entity method 2: Get P1419 of wikidata entity: 'Universe' // const wiki = new Wikiapi; // Read, access by title (English), access property P1419 let data = await wiki.data('Universe', 'P1419'); // assert: {Array}data = [ 'shape of the universe', '', ... ] console.assert(data.includes('shape of the universe')); // * * @example update wikidata // // Just for test delete CeL.wiki.query.default_maxlag; const wiki = new Wikiapi; await wiki.login('user', 'password', 'test'); // Get https://test.wikidata.org/wiki/Q7 let entity = await wiki.data('Q7'); // search [ language, label ] //entity = await wiki.data(['en', 'Earth']); // Reset claim entity = await wiki.data('Q1841'); await entity.modify({ claims: [{ P3: "old.wav", remove: true }] }, { bot: 1, summary: 'test edit: Remove specific value' }); // Warning: If you want to perform multiple operations on the same property, you need to get the entity again! entity = await wiki.data('Q1841'); await entity.modify({ claims: [{ P3: "new.wav" }] }, { bot: 1, summary: 'test edit: Add value' }); // Update claim await entity.modify({ claims: [{ P17: 'Q213280' }] }, { bot: 1, summary: 'test edit: Update claim' }); // Update claim: set country (P17) to 'Test Country 1' (Q213280) ([language, label] as entity) await entity.modify({ claims: [{ language: 'en', country: [, 'Test Country 1'] }] }, { summary: '' }); // Remove country (P17) : 'Test Country 1' (Q213280) await entity.modify({ claims: [{ language: 'en', country: [, 'Test Country 1'], remove: true }] }, { summary: '' }); // Update label await entity.modify({ labels: [{ language: 'zh-tw', value: '地球' }] }, { summary: '' }); // * * @memberof Wikiapi.prototype */ function Wikiapi_data(key, property, options) { if (CeL.is_Object(property) && !options) { // shift arguments. [property, options] = [null, property]; } function Wikiapi_data_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; if (false && wiki_API.is_page_data(key)) { // get entity (wikidata item) of page_data: key // .page(key): 僅僅設定 .last_page,不會真的再獲取一次頁面內容。 wiki.page(key); } if (key.title && !key.site) { // @see function wikidata_entity() @ CeL.application.net.wiki.data // 確保引用到的是本 wiki session,不會引用到其他 site。 key = { ...key, site: this.site_name() }; } // using wikidata_entity() → wikidata_datavalue() wiki.data(key, property, (data_entity, error) => { if (error) { reject(error); } else { setup_data_entity.call(wiki, data_entity); resolve(data_entity); } }, options); } return new Promise(Wikiapi_data_executor.bind(this)); } /** * @alias new_data_entity * @description Create new entity or property * * @param {Object} data_to_modify - Initial data. * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Object} new entity or property. * * @example Create new entity // const new_entity = await wiki.new_data_entity({ labels: { en: "Evolution in Mendelian Populations" }, P698: "17246615", P932: "1201091" }, { new: 'item' }); // * * @memberof Wikiapi.prototype */ function Wikiapi_new_data_entity(data_to_modify, options) { function Wikiapi_new_data_entity_executor(resolve, reject) { options = { new: 'item', ...options }; const wiki = this[KEY_wiki_session]; wiki.edit_data({}, options, (data_entity, error) => { if (error) { reject(error); } else if (data_to_modify) { delete options.new; //console.trace([data_entity, options]); wiki.edit_data(data_entity, data_to_modify, options, (result, error) => { if (error) { reject(error); } else if (false && options.retrieve_entity) { // reget modified data this.data(data_entity.id, options).then(resolve, reject); } else { //console.trace([data_entity, result]); //data_entity.latest_result = result; // data_entity: e.g., // {"type":"item","id":"Q123456","labels":{},"descriptions":{},"aliases":{},"claims":{},"sitelinks":{},"lastrevid":123456} resolve(data_entity); } }); } else { setup_data_entity.call(wiki, data_entity); resolve(data_entity); } }); } return new Promise(Wikiapi_new_data_entity_executor.bind(this)); } // -------------------------------------------------------- /** * @alias SPARQL * @description Query wikidata via SPARQL * * @param {Object} SPARQL - SPARQL to query. Please test it on Wikidata Query Service first. * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Array} query result of `SPARQL`. * * @example Get cats // const wikidata_item_list = await wiki.SPARQL(` SELECT ?item ?itemLabel WHERE { ?item wdt:P31 wd:Q146. SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } } `); // * * @example Get specific DOI // // for case-insensitive DOI const wikidata_item_list = await wiki.search('haswbstatement:' + JSON.stringify('P356=10.1371/journal.pone.0029797'), { namespace: 0 }); //wikidata_item_list.map(item => item.title) // for case-sensitive DOI const wikidata_item_list = await wiki.SPARQL(` SELECT ?doi ?item ?itemLabel WHERE { VALUES ?doi { "10.1371/JOURNAL.PONE.0029797" } ?item wdt:P356 ?doi. SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } }`, { // options.API_URL: custom SPARQL endpoint API_URL: '' }); //wikidata_item_list.id_list() // * * @memberof Wikiapi.prototype */ function Wikiapi_SPARQL(SPARQL, options) { function Wikiapi_SPARQL_executor(resolve, reject) { wiki_API.SPARQL(SPARQL, (result, error) => { if (error) { reject(error); } else { resolve(result); } }, this.append_session_to_options(options)); } return new Promise(Wikiapi_SPARQL_executor.bind(this)); } // -------------------------------------------------------- /** * * @example get list of [[w:en:Category:Chemical_elements]] // const wiki = new Wikiapi; let list = await wiki.categorymembers('Chemical elements'); console.log(list); // Working on multiple pages await wiki.for_each_page( // {Array} title liat / page data list list, page_data => { // ... }); // * * @example get pages transcluding {{w:en:Periodic table}} // const wiki = new Wikiapi; let list = await wiki.embeddedin('Template:Periodic table'); console.log(list); // */ // Warning: Won't throw if title is not existed! // @inner function Wikiapi_list(list_type, title, options) { function Wikiapi_list_executor(resolve, reject) { options = CeL.setup_options(options); // const wiki = this[KEY_wiki_session]; wiki_API.list(title, (list/* , target, options */) => { // console.trace(list); if (list.error) { reject(list.error); } else { resolve(list); } }, this.append_session_to_options({ type: list_type, // namespace: '0|1', ...options })); /** * // method 2: 使用循環取得資料版: wiki.cache({ // Do not write cache file to disk. cache: false, type: list_type, list: title }, (list, error) => { if (error) { reject(error); } else { resolve(list); } }, // default options === this //{ namespace : '0|1' } options); // NG: 不應使用單次版 wiki[list_type](title, (list, error) => { if (error) { reject(error); } else { resolve(list); } }, { limit: 'max', ...options }); */ } return new Promise(Wikiapi_list_executor.bind(this)); } // functions for several kinds of lists function Wikiapi_for_each(type, title, for_each, options) { return Wikiapi_list.call(this, type, title, { for_each, ...options }); } // -------------------------------------------------------- /** * @alias category_tree * @description Get structural category tree with sub-categories of root_category. This is powerful than categorymembers. Get sub-categories with {@link Wikiapi.KEY_subcategories}. * * @param {String} root_category - category name * @param {Object} [options] - options to run this function. * * @returns {Promise} Promise object represents {Array} category_tree. * * @example Checking if [[Category:Countries in North America]] including [[Mexico]]. // const enwiki = new Wikiapi('en'); const page_list = await enwiki.category_tree('Countries in North America', 1); assert(page_list.some(page_data => page_data.title === 'United States'), 'list category tree: [[Category:Countries in North America]] must includes [[United States]]'); assert('Mexico' in page_list[Wikiapi.KEY_subcategories], 'list category tree: [[Category:Mexico]] is a subcategory of [[Category:Countries in North America]]'); // * * @example Get all sub-categories of [[Category:Echinodermata]] with depth=2. // const wiki = new Wikiapi('commons'); const all_sub_categories = (await wiki.category_tree('Echinodermata', { depth: 2, cmtype: 'subcat', get_flated_subcategories: true })).flated_subcategories; // * * @memberof Wikiapi.prototype */ function Wikiapi_category_tree(root_category, options) { function Wikiapi_category_tree_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; // using wiki_API.prototype.category_tree wiki.category_tree(root_category, (list, error) => { if (error) { reject(error); } else { resolve(list); } }, options); } return new Promise(Wikiapi_category_tree_executor.bind(this)); } /** * export key for subcategory 子分類 used in {@link Wikiapi#category_tree} * * @example // const KEY_subcategories = Wikiapi.KEY_subcategories; // */ Wikiapi.KEY_subcategories = wiki_API.KEY_subcategories; // -------------------------------------------------------- /** * @alias search * @description search pages include key * * @param {String} key - key to search * @param {Object} [options] - options to run this function. * * @returns {Promise} Promise object represents {Array} page_list. * * @example search pages include key: 霍金 // const zhwikinews = new Wikiapi('zh.wikinews'); const page_list = await zhwikinews.search('"霍金"'); // * * @memberof Wikiapi.prototype */ function Wikiapi_search(key, options) { function Wikiapi_search_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; // using wiki_API.search wiki.search(key, (list, error) => { if (error) { reject(error); } else { resolve(list); } }, options); } return new Promise(Wikiapi_search_executor.bind(this)); } // -------------------------------------------------------- /** * @alias redirects_root * @description Get redirects target of title. * * @param {String} title - page title * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {String} page title or {Object} page data * * @example Get redirects target of [[WP:SB]] // const redirects_taregt = await enwiki.redirects_root('WP:SB', { get_page_data: true }); // * * @memberof Wikiapi.prototype */ function Wikiapi_redirects_root(title, options) { function Wikiapi_redirects_root_executor(resolve, reject) { // const wiki = this[KEY_wiki_session]; // using wiki_API.redirects_root wiki_API.redirects_root(title, (_title, page_data, error) => { if (error) { reject(error); } else if (options && options.get_page_data) { page_data.query_title = title; resolve(page_data); } else { resolve(_title); } }, this.append_session_to_options(options)); } return new Promise(Wikiapi_redirects_root_executor.bind(this)); } // -------------------------------------------------------- /** * @alias redirects_here * @description Get all pages redirects to title. * * @param {String} title - page title * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents {Array} redirect_list * * @example Get all pages redirects to [[Wikipedia:Sandbox]] // const redirects_list = await enwiki.redirects_here('Wikipedia:Sandbox'); // * * @memberof Wikiapi.prototype */ function Wikiapi_redirects_here(title, options) { function Wikiapi_redirects_here_executor(resolve, reject) { // const wiki = this[KEY_wiki_session]; // using wiki_API.redirects_here wiki_API.redirects_here(title, (root_page_data, redirect_list, error) => { if (error) { reject(error); } else { //console.trace(root_page_data); //console.trace(redirect_list); //console.assert(!redirect_list || redirect_list === root_page_data.redirect_list); resolve(redirect_list || root_page_data); } }, this.append_session_to_options({ // Making .redirect_list[0] the redirect target. include_root: true, ...options })); } return new Promise(Wikiapi_redirects_here_executor.bind(this)); } // -------------------------------------------------------- /** * @alias register_redirects * @description register page alias. usually used for templates * * @param {Array|String} page_title_list - list of page titles * @param {Object} [options] - options to run this function. * * @returns {Promise} Promise object represents the operations are done. * * @example Register template redirects and get tokens of the templates. // const wiki_session = new Wikiapi; // e.g., await wiki_session.register_redirects(['Section link', 'Broken anchors'], { namespace: 'Template' }); await wiki_session.register_redirects([template_name_1, template_name_2, template_name_3], { namespace: 'Template' }); // ... const page_data = await wiki_session.page(page_title); // {Array} parsed page content 頁面解析後的結構。 const parsed = page_data.parse(); parsed.each('Template:' + template_name_1, function (token, index, parent) { // ... }); parsed.each('template', function (token, index, parent) { if (wiki_session.is_template(template_name_1, token)) { // ... return; } if (wiki_session.is_template(template_name_2, token)) { // ... return; } // alternative method: switch (wiki_session.redirect_target_of(token)) { case wiki_session.redirect_target_of(template_name_1): break; case wiki_session.redirect_target_of(template_name_2): break; case wiki_session.redirect_target_of(template_name_3): break; } }); // * * @memberof Wikiapi.prototype */ function Wikiapi_register_redirects(page_title_list, options) { function Wikiapi_register_redirects_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; wiki.register_redirects(page_title_list, (redirect_list, error) => { if (error) { reject(error); } else { // console.trace( redirect_list); resolve(redirect_list); } }, options); } return new Promise(Wikiapi_register_redirects_executor.bind(this)); } // -------------------------------------------------------- /** * @alias upload * @description Upload specified local file to the target wiki. * * @param {Object} file_data - Upload configurations.
* Warning: When you are update a file, only the file content will changed. The comment will only show in the file page. The text, ... till categories will all ignored. If you want to update the content of file page, please consider Variable_Map as mentioned in the sample code.
{
}

See edit.js and search for file_data for other file_data options. * * @returns {Promise} Promise object represents {String} result of MediaWiki API * * @example Upload file / media // const wiki = new Wikiapi; await wiki.login('user', 'password', 'test'); // Upload a local file directly: //let result = await wiki.upload({ file_path: '/local/file/path', comment: '', text: '' || {description: '', ...} }); let result = await wiki.upload({ file_path: '/local/file/path', comment: '', filename: 'Will set via .file_path or .media_url if not settled.', description: '', date: new Date() || '2021-01-01', source_url: 'https://github.com/kanasimi/wikiapi', author: '[[User:user]]', permission: '{{cc-by-sa-2.5}}', other_versions: '', other_fields: '', license: ['{{cc-by-sa-2.5}}'], categories: ['[[Category:test images]]'], bot: 1, tags: "tag1|tag2", // To overwrite existing file ignorewarnings: 1, }); // Upload file from URL: result = await wiki.upload({ media_url: 'https://media.url/name.jpg', comment: '', text: '' }); // * * @example Upload file and then update content of file page // const wiki = new Wikiapi; await wiki.login('user', 'password', 'test'); const variable_Map = new CeL.wiki.Variable_Map(); variable_Map.set('description', '...'); //variable_Map.set('date', '...'); // ... //variable_Map.set('other_fields', '...'); let result = await wiki.upload({ file_path: '/local/file/path', // The comment will only show in the file page when updating file. It is read-only and cannot be modified. comment: '', // CeL.wiki.Variable_Map is used to update content when update pages or files. It will insert comments around the value, prevent others from accidentally editing the text that will be overwritten. // description till other_fields will be auto-setted as values assigned above. // The code to do the conversion is in wiki_API.upload @ https://github.com/kanasimi/CeJS/blob/master/application/net/wiki/edit.js // There are some examples: https://github.com/kanasimi/wikibot/blob/master/routine/20181016.import_earthquake_shakemap.js https://github.com/kanasimi/wikibot/blob/master/routine/20190629.import_hurricane_track_maps.js // More examples to use CeL.wiki.Variable_Map: https://github.com/kanasimi/wikibot/blob/master/routine/20191129.check_language_convention.js variable_Map, // When set .variable_Map, after successful update, the content of file page will be auto-updated too. // To overwrite existing file ignorewarnings: 1, }); // * * @memberof Wikiapi.prototype */ function Wikiapi_upload(file_data) { // 2021/3/25 renamed from old name: Wikiapi_upload_file(), // Wikiapi_upload_file_executor() function Wikiapi_upload_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; wiki.upload(file_data, (result, error) => { if (error) { reject(error); } else { resolve(result); } }); } return new Promise(Wikiapi_upload_executor.bind(this)); } // -------------------------------------------------------- /** * @alias download * @description Download file to local path. * * @param {String} file_title - file title starts with "File:" * @param {Object} [options] - options to run this function. Refer to example codes. * * @returns {Promise} Promise object represents [ {Object}file informations ] * * @example Download original file / media to current directory. // const wiki = new Wikiapi('commons'); await wiki.download('File:Example.svg'); // * * @example Download file / media with options // const wiki = new Wikiapi('commons'); // Download non-vector version of .svg await wiki.download('File:Example.svg', { width: 80 }); // Change width / height await wiki.download('File:Example.png', { file_name: 'example.png', directory: '/tmp/', // reget and overwrite existed file. reget: true, width: 80,// height: 80 }); // Download all files from a (Commons) category and its subcategories WITH directory structure. const file_data_list = await wiki.download('Category:name', { directory: './', max_threads: 4, // depth of categories depth: 4, // Only download files with these formats. //download_derivatives : ['wav', 'mp3', 'ogg'], // Warning: Will skip downloading if there is no new file! download_derivatives : 'mp3', // A function to filter result pages. Return `true` if you want to keep the element. page_filter(page_data) { return page_data.title.includes('word'); } }); // Download all files from a (Commons) category WITHOUT directory structure. for (const page_data of await wiki.categorymembers('Category:name', { namespace: 'File' })) { try { //if (wiki.is_namespace(page_data, 'File')) const file_data = await wiki.download(page_data, { directory: './' }); } catch (e) { console.error(e); } } // also const categorymembers = await wiki.categorymembers('Category:name', { namespace: 'File' }); const file_data_list = await wiki.download(categorymembers, { directory: './', no_category_tree: true }); // * * @memberof Wikiapi.prototype */ function Wikiapi_download(file_title, options) { function Wikiapi_download_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; wiki.download(file_title, options, (result, error) => { if (error) { // return result.error_titles reject(result && result.error_titles && result || error); } else { resolve(result); } }); } return new Promise(Wikiapi_download_executor.bind(this)); } // -------------------------------------------------------- /** * @alias for_each_page * @description Edit / process pages listing in page_list. Will get the content of multiple pages at once to save transmission times. 一次取得多個頁面內容,以節省傳輸次數。 * * @param {Array} page_list - title list or page_data list * @param {Function} for_each_page - processor for each page. for_each_page(page_data with contents) * @param {Object} [options] - options to run this function. Refer to example codes. * * @returns {Promise} Promise object represents the operations are done. * * @example read / edit multiple pages // const enwiki = new Wikiapi('en'); const link_from = await wiki.redirects_here('ABC'); await wiki.for_each_page(link_from, page_data => { // Return `Wikiapi.skip_edit` if you just want to get the page data. return Wikiapi.skip_edit; return 'You may also modify page contents for each page'; }, { // The options below are sample, not default configuration. // denotes we do not edit pages no_edit: true, // Only needed if you want to modify page. summary: 'test edit', // Allow content to be emptied. 允許內容被清空。白紙化。 allow_empty: true, tags: 'bot trial', // prevent creating new pages // Throw an error if the page doesn't exist. // 若頁面不存在/已刪除,則產生錯誤。 nocreate: 1, // denotes this is a bot edit. 標記此編輯為機器人編輯。 bot: 1, minor: 1, // options to get page revisions page_options: { redirects: 1, rvprop: 'ids|content|timestamp|user' } // .for_each_page() will generate a report. It can be written to the specified page. log_to: 'log to this page', // no warning messages on console. e.g., hide "wiki_API_page: No contents: [[title]]" messages no_warning: true, // no warning messages and debug messages on console no_message: true, }); // * * @memberof Wikiapi.prototype */ function Wikiapi_for_each_page(page_list, for_each_page, options) { function Wikiapi_for_each_page_executor(resolve, reject) { options = typeof options === 'string' ? { summary: options } : CeL.setup_options(options); const wiki = this[KEY_wiki_session]; const append_to_this = Array.isArray(for_each_page) && for_each_page[1]; if (Array.isArray(for_each_page)) for_each_page = for_each_page[0]; // console.trace(for_each_page); const work_config = { log_to: null, no_message: options.no_edit, ...options, //is_async_each: CeL.is_async_function(for_each_page), each: [function each(page_data/* , messages, config */) { set_page_data_attributes(page_data, wiki); return for_each_page.apply(this, arguments); }, append_to_this], // Run after all list items (pages) processed. last(error) { // this === options // console.trace('last(error)'); // console.error(error); // console.trace('Wikiapi_for_each_page_executor finish:'); // console.log(options); // 提早執行 resolve(), reject() 的話,可能導致後續的程式碼 `options.last` // 延後執行,程式碼順序錯亂。 if (typeof options.last === 'function') options.last.call(this, error); if (error) { if (options.throw_error) { reject(error); return; } console.error(error); } resolve(this); } }; wiki.work(work_config, page_list); } return new Promise(Wikiapi_for_each_page_executor.bind(this)); } // -------------------------------------------------------- /** * @alias convert_Chinese * @description convert text to traditional Chinese / simplified Chinese. * * @param {String|Array|Object} text - text or objects to convert. Will convert to {String} using JSON.stringify(). * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents the converted text. * * @example 繁簡轉換 // const wiki = new Wikiapi('en'); await wiki.convert_Chinese('中国', { uselang: 'zh-hant' }); await wiki.convert_Chinese('中國', { uselang: 'zh-hans' }); await wiki.convert_Chinese(['繁體', '簡體'], { uselang: 'zh-hans' }); // * * @memberof Wikiapi.prototype */ function Wikiapi_convert_Chinese(text, options) { function Wikiapi_convert_Chinese(resolve, reject) { if (typeof options === 'string') { options = { uselang: options }; } const site_name = this.site_name({ get_all_properties: true }); // node.js v12.22.7: Cannot use "?." if (site_name && site_name.language === 'zh') { // 不用再重新造出一個實體。 options = this.append_session_to_options(options); } // using wiki_API.search wiki_API.convert_Chinese(text, (text, error) => { if (error) { reject(error); } else { resolve(text); } }, options); } return new Promise(Wikiapi_convert_Chinese.bind(this)); } // -------------------------------------------------------- // May only test in the [https://tools.wmflabs.org/ Wikimedia Toolforge] function Wikiapi_run_SQL(SQL, for_each_row/* , options */) { function Wikiapi_run_SQL_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; function run_callback() { wiki.SQL_session.SQL(SQL, (error, rows/* , fields */) => { if (error) { reject(error); } else { rows.forEach(for_each_row); } }); resolve(); } if (wiki.SQL_session) { run_callback(); return; } wiki.SQL_session = new wiki_API.SQL((error, rows, fields) => { if (error) { reject(error); } else { run_callback(); } }, wiki); } return new Promise(Wikiapi_run_SQL_executor.bind(this)); } // -------------------------------------------------------- function Wikiapi_setup_layout_elements(options) { function Wikiapi_setup_layout_elements_executor(resolve, reject) { // const wiki = this[KEY_wiki_session]; wiki_API.setup_layout_elements(resolve, this.append_session_to_options(options)); } return new Promise(Wikiapi_setup_layout_elements_executor.bind(this)); } // -------------------------------------------------------- /** * @alias get_featured_content * @description Get featured content. * * @param {String|Object} [options] - options to run this function. * {String}type (FFA|GA|FA|FL) * || {type,on_conflict(FC_title, {from,to})} * * @returns {Promise} Promise object represents {Object} featured content data hash * * @example Get featured content of current wiki site. // // MUST including wiki.featured_content first to get featured content! CeL.run('application.net.wiki.featured_content'); // ... const FC_data_hash = await wiki.get_featured_content(); console.assert(FC_data_hash === wiki.FC_data_hash); // * * @memberof Wikiapi.prototype */ function Wikiapi_get_featured_content(options) { if (!options || !options.type) { const session = this; return Wikiapi_get_featured_content.default_types .reduce((promise, type) => promise.then(Wikiapi_get_featured_content.bind(session, { ...options, type })), Promise.resolve()); if (false) { let promise = Promise.resolve(); Wikiapi_get_featured_content.default_types.forEach(type => { promise = promise.then(Wikiapi_get_featured_content.bind(session, { ...options, type })); }); return promise; } } function Wikiapi_get_featured_content_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; wiki.get_featured_content(options, FC_data_hash => { try { this.FC_data_hash = FC_data_hash; resolve(FC_data_hash); } catch (e) { reject(e); } }); } return new Promise(Wikiapi_get_featured_content_executor.bind(this)); } Wikiapi_get_featured_content.default_types = 'FFA|GA|FA|FL'.split('|'); // -------------------------------------------------------- /** * @alias site_name * @description Get site name / project name of this {Wikiapi}. * * @param {String} [language] - language code of wiki session * @param {Object} [options] - options to run this function * * @returns {String}site name * * @example Get site name of {Wikiapi}. // console.log(Wikiapi.site_name('zh', { get_all_properties: true })); const wiki = new Wikiapi('en'); console.assert(wiki.site_name() === 'enwiki'); console.log(wiki.site_name({ get_all_properties: true })); console.assert(wiki.site_name({ get_all_properties: true }).language === 'en'); // * * @memberof Wikiapi.prototype */ function Wikiapi_site_name(language, options) { return wiki_API.site_name(language, options); } Wikiapi.site_name = Wikiapi_site_name; // -------------------------------------------------------- // administration functions 管理功能。 /** * @alias delete * @description delete page * * @param {String} title - page title * @param {Object} [options] - options to run this function * * @returns {Promise} Promise object represents response of delete. * * @example delete page [[Page to delete]] // const testwiki = new Wikiapi('test'); await testwiki.delete('Page to delete', { reason: 'test' }); // { title: 'Aaaaaaa', reason: 'test', logid: 346223 } // * * @memberof Wikiapi.prototype */ function Wikiapi_delete(title, options) { function Wikiapi_delete_executor(resolve, reject) { const wiki = this[KEY_wiki_session]; // using wiki_API.delete wiki.page(title).delete(options, (response, error) => { if (error) { reject(error); } else { resolve(response); } }, options); } return new Promise(Wikiapi_delete_executor.bind(this)); } // -------------------------------------------------------- // exports Object.assign(Wikiapi.prototype, { append_session_to_options(options) { // Object.assign({ [KEY_SESSION]: wiki }, options) // return { ...options, [KEY_SESSION]: this[KEY_wiki_session] }; return wiki_API.add_session_to_options(this[KEY_wiki_session], options); }, site_name(options) { return Wikiapi_site_name(this[KEY_wiki_session], options); }, login: Wikiapi_login, query: Wikiapi_query, page: Wikiapi_page, tracking_revisions: Wikiapi_tracking_revisions, edit_page: Wikiapi_edit_page, /** * @description edits content of target page.
* MUST using after {@link Wikiapi#page}!
* Note: for multiple pages, you should use {@link Wikiapi#for_each_page}.
* Note: The function will check sections of [[User talk:user name/Stop]] if somebody tells us needed to stop edit. See mechanism to stop operations. * * @param {String|Function} content - 'wikitext page content' || page_data => 'wikitext' * @param {Object} [options] - options to run this function. e.g., { summary: '', bot: 1, nocreate: 1, minor: 1 } * * @returns {Promise} Promise object represents {Object} result of MediaWiki API * * @memberof Wikiapi.prototype */ edit(content, options) { return this.edit_page(null, content, options); }, move_to: Wikiapi_move_to, move_page: Wikiapi_move_page, purge: Wikiapi_purge, /** * @description Listen to page modification. 監視最近更改的頁面。
* wrapper for {@link wiki_API}#listen * * @param {Function} listener - function(page_data) { return quit_listening; } * @param {Object} [options] - options to run this function. e.g., { summary: '', bot: 1, nocreate: 1, minor: 1 } * * @example listen to new edits // const wiki = new Wikiapi; wiki.listen(function for_each_row() { // ... }, { // 檢查的延遲時間。 delay: '2m', filter: function filter_row(row) { // row is the same format as page_data }, // also get diff with_diff: { LCS: true, line: true }, // only for articles (0:main namespace) and talk pages namespace: '0|talk', }); // * * @memberof Wikiapi.prototype */ listen(listener, options) { const wiki = this[KEY_wiki_session]; wiki.listen(listener, options); }, category_tree: Wikiapi_category_tree, search: Wikiapi_search, redirects_root: Wikiapi_redirects_root, // Warning: 採用 wiki_API.redirects_here(title) 才能追溯重新導向的標的。 // wiki.redirects() 無法追溯重新導向的標的! redirects_here: Wikiapi_redirects_here, register_redirects: Wikiapi_register_redirects, upload: Wikiapi_upload, download: Wikiapi_download, get_featured_content: Wikiapi_get_featured_content, for_each_page: Wikiapi_for_each_page, for_each: Wikiapi_for_each, delete: Wikiapi_delete, data: Wikiapi_data, new_data_entity: Wikiapi_new_data_entity, SPARQL: Wikiapi_SPARQL, convert_Chinese: Wikiapi_convert_Chinese, run_SQL: Wikiapi_run_SQL, setup_layout_elements: Wikiapi_setup_layout_elements, }); // wrapper for properties for (const property_name of ('task_configuration|latest_task_configuration').split('|')) { Object.defineProperty(Wikiapi.prototype, property_name, { get() { const wiki = this[KEY_wiki_session]; return wiki[property_name]; } }); } // wrapper for sync functions for (const function_name of ('namespace|remove_namespace|is_namespace|to_namespace|is_talk_namespace|to_talk_page|talk_page_to_main|normalize_title|redirect_target_of|aliases_of_page|is_template' // CeL.run('application.net.wiki.featured_content'); // [].map(wiki.to_talk_page.bind(wiki)) + '|get_featured_content_configurations').split('|')) { Wikiapi.prototype[function_name] = function wrapper() { const wiki = this[KEY_wiki_session]; return wiki[function_name].apply(wiki, arguments); }; } // @see get_list.type @ // https://github.com/kanasimi/CeJS/blob/master/application/net/wiki/list.js for (const type of wiki_API.list.type_list) { // Cannot use `= (title, options) {}` ! // arrow function expression DO NOT has this, arguments, super, or // new.target keywords. Wikiapi.prototype[type] = function (title, options) { const _this = this; /** * @example const page_list = await wiki.embeddedin(template_name, options); await page_list.each((page_data) => { }, options); * */ return Wikiapi_list.call(this, type, title, options) .then((page_list) => { // console.log(page_list); page_list.each = Wikiapi_for_each_page.bind(_this, page_list); return page_list; }); }; } module.exports = Wikiapi; // export default Wikiapi;