post_fsd_wiki.phantomjs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*\ post_fsd_wiki.phantomjs
  2. |*|
  3. |*| this script is used to create/modify mediawiki pages on the FSD
  4. |*|
  5. |*| one such wiki page for each blacklisted package
  6. |*|
  7. |*| it must be executed by the phantomjs program
  8. |*| and have access to a JSON file as created by the report.rb script
  9. \*/
  10. var BLACKLIST_DATA_FILE = './blacklist-testdata.json' ;
  11. var WIKI_TEMPLATE_FILE = './wiki-template.json' ;
  12. var WIKI_BASE_URL = 'http://localhost/mediawiki/index.php' ;
  13. // var WIKI_BASE_URL = 'https://directory.fsf.org/wiki?title=' ;
  14. var LOGIN_URL = WIKI_BASE_URL + '?title=Special:UserLogin' ;
  15. var EDIT_URL = WIKI_BASE_URL + '?title=_TITLE_&action=edit' ;
  16. var USAGE_MSG = "USAGE: phantomjs ./post_fsd_wiki.phantomjs <WIKI_LOGIN> <WIKI_PASS>" ;
  17. var TITLE_URL_REGEX = /_TITLE_/ ;
  18. var PARABOLA_ENTRY_HEADER = '== Parabola Blacklist Description ==\n' ;
  19. var WIKI_TEXT_BEGIN = '<!-- PARABOLA BLACKLIST BEGIN (DO NOT EDIT) -->' + PARABOLA_ENTRY_HEADER ;
  20. var WIKI_TEXT_END = '\n<!-- PARABOLA BLACKLIST END -->\n' ;
  21. var WIKI_TEXT_REGEX = RegExp(WIKI_TEXT_BEGIN.replace(/\((.*)\)/ , '\\($1\\)') + '(.|\n)*' + WIKI_TEXT_END) ;
  22. var LOGIN_IMPUT_ID = 'wpName1' ;
  23. var PASS_IMPUT_ID = 'wpPassword1' ;
  24. var LOGIN_SUBMIT_IMPUT_ID = 'wpLoginAttempt' ;
  25. var USERNAME_LI_ID = 'pt-userpage' ;
  26. var EDIT_TEXT_INPUT_ID = 'wpTextbox1' ;
  27. var EDIT_SUBMIT_INPUT_ID = 'wpSave' ;
  28. var WIKI_TITLE_H1_ID = 'firstHeading' ;
  29. var WIKI_TEXT_DIV_ID = 'mw-content-text' ;
  30. var CATEGORIES_DIV_ID = 'catlinks' ;
  31. var PACKAGE_NAME_KEY = 'original_package' ; // BLACKLIST_DATA_FILE JSON per ./SYNTAX CSV[0]
  32. var REPLACEMENT_KEY = 'libre_replacement' ; // BLACKLIST_DATA_FILE JSON per ./SYNTAX CSV[1]
  33. var REFERENCE_KEY = 'ref' ; // BLACKLIST_DATA_FILE JSON per ./SYNTAX CSV[2]
  34. var ENTRY_ID_KEY = 'id' ; // BLACKLIST_DATA_FILE JSON per ./SYNTAX CSV[3]
  35. var DESCRIPTION_KEY = 'short_description' ; // BLACKLIST_DATA_FILE JSON per ./SYNTAX CSV[4]
  36. var BLACKLIST_TAGS_KEY = 'blacklist_tags' // BLACKLIST_DATA_FILE JSON
  37. var NONFREE_TAG = 'nonfree' ;
  38. var SEMIFREE_TAG = 'semifree' ;
  39. var USES_NONFREE_TAG = 'uses-nonfree' ;
  40. var BRANDING_TAG = 'branding' ;
  41. var TECHNICAL_TAG = 'technical' ;
  42. var HAS_REPLACEMENT_TAG = 'FIXME:package' ;
  43. var NEEDS_DESC_TAG = 'FIXME:description' ;
  44. var ACCEPTED_TAGS = [ NONFREE_TAG , SEMIFREE_TAG , USES_NONFREE_TAG ,
  45. HAS_REPLACEMENT_TAG , NEEDS_DESC_TAG ] ;
  46. var STEP_FUNCTION_KEY = 'step-function' ;
  47. var PAGELOAD_WAIT_KEY = 'wait-for-pageload' ;
  48. var STEP_TIMEOUT = 3000 ; var StepTimeout ;
  49. var System = require('system') ;
  50. var Args = System.args ;
  51. var Page = require('webpage').create() ;
  52. var WIKI_LOGIN = Args[1] || '' ;
  53. var WIKI_PASS = Args[2] || '' ;
  54. var WikiPages = [] ; var WikiPage ;
  55. var Steps = [] ; var Step ;
  56. var StepN = 0 ;
  57. var IsReady = true ;
  58. var ShouldWait = false ;
  59. var ShouldQuit = false ;
  60. if (WIKI_LOGIN == '' || WIKI_PASS == '') { LOG(USAGE_MSG) ; phantom.exit() ; } LOG('') ;
  61. /* steps */
  62. function Prepare()
  63. {
  64. LOG("loading BLACKLIST_DATA_FILE") ; var blacklist_data = require(BLACKLIST_DATA_FILE) ;
  65. if (!IsA(blacklist_data , Array )) { ForceQuit("failed to load package data") ; return ; }
  66. LOG("found " + blacklist_data.length + " packages") ;
  67. blacklist_data.forEach(function(package_data)
  68. {
  69. var package_name = package_data[PACKAGE_NAME_KEY ] && package_data[PACKAGE_NAME_KEY ].trim() ;
  70. var replacement = package_data[REPLACEMENT_KEY ] && package_data[REPLACEMENT_KEY ].trim() ;
  71. var description = package_data[DESCRIPTION_KEY ] && package_data[DESCRIPTION_KEY ].trim() ;
  72. var blacklist_tags = package_data[BLACKLIST_TAGS_KEY] ;
  73. if ((!IsA(package_name , String) ) ||
  74. (!IsA(replacement , String) && replacement != '') ||
  75. (!IsA(description , String) ) ||
  76. (!IsA(blacklist_tags , Array ) ) )
  77. { LOG_ERR("invalid package data - ignoring:\n" + JSON.stringify(package_data)) ; return ; }
  78. package_name = package_name.toLowerCase().replace(/ /g , '_') ;
  79. var replacement_text = (!!replacement) ? '*Parabola Replacement: ' + replacement + '\n\n' : '' ;
  80. blacklist_tags = blacklist_tags.filter(function(tag) { return ~ACCEPTED_TAGS.indexOf(tag) ; })
  81. .map (function(tag) { return 'FSDG_' + tag }) ;
  82. if (blacklist_tags.length == 0) { LOG(package_name + " tags are uninteresting - ignoring") ; return ; }
  83. LOG("loading package data: " + package_name) ;
  84. var wiki_page = {} ;
  85. wiki_page[PACKAGE_NAME_KEY ] = package_name ;
  86. wiki_page[REPLACEMENT_KEY ] = replacement_text
  87. wiki_page[DESCRIPTION_KEY ] = description ;
  88. wiki_page[BLACKLIST_TAGS_KEY] = blacklist_tags ;
  89. WikiPages.push(wiki_page) ;
  90. }) ;
  91. IsReady = true ;
  92. }
  93. function LoadLoginPage()
  94. {
  95. if (WikiPages.length > 0) { LOG(WikiPages.length + " WikiPages to consider") ; }
  96. else { ForceQuit("no WikiPages to consider") ; return ; }
  97. GetUrl(LOGIN_URL) ;
  98. }
  99. function SubmitLogin()
  100. {
  101. EvalQuitOnErr(Page.evaluate(function(login_input_id , pass_input_id , submit_input_id ,
  102. wiki_login , wiki_pass , IsA )
  103. {
  104. var login_input = document.getElementById(login_input_id ) ;
  105. var pass_input = document.getElementById(pass_input_id ) ;
  106. var submit_button = document.getElementById(submit_input_id) ;
  107. var login_form = submit_button.form ;
  108. if (!IsA(login_input , 'INPUT' ) || !IsA(pass_input , 'INPUT') ||
  109. !IsA(submit_button , 'BUTTON') || !IsA(login_form , 'FORM' ) )
  110. return "ERROR: invalid login page" ;
  111. login_input.value = wiki_login ;
  112. pass_input.value = wiki_pass ;
  113. login_form.submit() ;
  114. return '' ;
  115. } , LOGIN_IMPUT_ID , PASS_IMPUT_ID , LOGIN_SUBMIT_IMPUT_ID , WIKI_LOGIN , WIKI_PASS , IsA)) ;
  116. IsReady = true ;
  117. }
  118. function VerifyLogin()
  119. {
  120. EvalQuitOnErr(Page.evaluate(function(username_li_id , wiki_login , IsA , LOG)
  121. {
  122. var username_li = document.getElementById(username_li_id) ;
  123. var username = !!username_li && username_li.textContent.toLowerCase() ;
  124. if (!IsA(username_li , 'LI') ||
  125. username != wiki_login.toLowerCase())
  126. return "ERROR: login failed" ;
  127. LOG("signed in as: " + username) ; return '' ;
  128. } , USERNAME_LI_ID , WIKI_LOGIN , IsA , LOG)) ;
  129. IsReady = true ;
  130. }
  131. function LoadWikiData()
  132. {
  133. LOG(WikiPages.length + " WikiPages remaining") ;
  134. if (!!(WikiPage = WikiPages.shift())) IsReady = true ; else Done() ;
  135. }
  136. function LoadEditPage()
  137. {
  138. var page_title = WikiPage[PACKAGE_NAME_KEY] ;
  139. GetUrl(EditPageUrl(page_title)) ;
  140. }
  141. function SubmitEdit()
  142. {
  143. var page_title = WikiPage[PACKAGE_NAME_KEY ] ;
  144. var replacement_text = WikiPage[REPLACEMENT_KEY ] ;
  145. var wiki_text = WikiPage[DESCRIPTION_KEY ] ;
  146. var wiki_categories = WikiPage[BLACKLIST_TAGS_KEY] ;
  147. wiki_text = WIKI_TEXT_BEGIN + replacement_text + wiki_text + '\n\n' ;
  148. wiki_categories.forEach(function(category) { wiki_text += '[[Category:' + category + ']]\n' }) ;
  149. wiki_text += WIKI_TEXT_END
  150. if (Page.url != EditPageUrl(page_title)) { ForceQuit("edit page load failed") ; return ; }
  151. var post_wiki_text = EvalQuitOnErr(Page.evaluate(function(wiki_text_input_id , wiki_submit_button_id ,
  152. wiki_text_regex , new_wiki_text ,
  153. IsA , DBG )
  154. {
  155. var wiki_text_input = document.getElementById(wiki_text_input_id ) ;
  156. var wiki_submit_button = document.getElementById(wiki_submit_button_id) ;
  157. var existing_wiki_text = !!wiki_text_input && wiki_text_input.value ;
  158. DBG('SubmitEditEvalIn')(wiki_text_input , wiki_submit_button) ;
  159. if (!IsA(wiki_text_input , 'TEXTAREA') ||
  160. !IsA(wiki_submit_button , 'INPUT' ) )
  161. return "ERROR: invalid edit page" ;
  162. var modified_wiki_text = existing_wiki_text.replace(wiki_text_regex , new_wiki_text) ;
  163. var concat_wiki_text = existing_wiki_text + new_wiki_text ;
  164. var post_wiki_text = (!wiki_text_regex.test(existing_wiki_text)) ? concat_wiki_text :
  165. (modified_wiki_text != existing_wiki_text ) ? modified_wiki_text : '' ;
  166. DBG('SubmitEditEvalOut')(new_wiki_text , existing_wiki_text , modified_wiki_text , post_wiki_text , wiki_text_regex) ;
  167. if (!!post_wiki_text) { wiki_text_input.value = post_wiki_text ; wiki_submit_button .click() ; }
  168. return post_wiki_text ;
  169. } , EDIT_TEXT_INPUT_ID , EDIT_SUBMIT_INPUT_ID , WIKI_TEXT_REGEX , wiki_text , IsA , DBG)) ;
  170. DBG('SubmitEditOut')(post_wiki_text) ;
  171. if (post_wiki_text == '') { LOG("wiki text has not changed - skipping") ; NextPage() ; }
  172. }
  173. function VerifyEdit()
  174. {
  175. var page_title = WikiPage[PACKAGE_NAME_KEY ] ;
  176. var replacement_text = WikiPage[REPLACEMENT_KEY ] ;
  177. var wiki_text = WikiPage[DESCRIPTION_KEY ] ;
  178. var wiki_categories = WikiPage[BLACKLIST_TAGS_KEY] ;
  179. var expected_url = WIKI_BASE_URL + '/' + page_title ;
  180. page_title = page_title .replace(/_/g , ' ') ;
  181. wiki_text = PARABOLA_ENTRY_HEADER .replace(/=/g , '').trim() + '\n' +
  182. replacement_text .replace(/\*/g , '').trim() + '\n' + wiki_text ;
  183. var categories = wiki_categories.join('').replace(/_/g , ' ') ;
  184. var categories_text = ((wiki_categories.length > 1) ? 'Categories: ' : 'Category: ') + categories ;
  185. if (DEBUG_VB) DUMPFILE() ;
  186. if (Page.url.toLowerCase() != expected_url) { ForceQuit("edit post expected_url failed") ; return ; }
  187. var err = EvalQuitOnErr(Page.evaluate(function(wiki_title_h1_id , wiki_text_div_id , categories_div_id ,
  188. page_title , new_wiki_text , new_categories ,
  189. IsA , DBG )
  190. {
  191. var wiki_title_h1 = document.getElementById(wiki_title_h1_id ) ;
  192. var wiki_text_div = document.getElementById(wiki_text_div_id ) ;
  193. var categories_div = document.getElementById(categories_div_id) ;
  194. var existing_wiki_title = !!wiki_title_h1 && wiki_title_h1 .textContent.toLowerCase() ;
  195. var existing_wiki_text = !!wiki_text_div && wiki_text_div .textContent.trim() ;
  196. var existing_categories = !!categories_div && categories_div.textContent.trim() ;
  197. var wiki_text_regex = RegExp(new_wiki_text ) ;
  198. var categories_regex = RegExp(new_categories) ;
  199. DBG('VerifyEdit')(wiki_title_h1 , wiki_text_div , categories_div , existing_wiki_title , page_title , existing_wiki_text , new_wiki_text , existing_categories , new_categories , wiki_text_regex , categories_regex) ;
  200. if (!IsA(wiki_title_h1 , 'H1' ) || existing_wiki_title != page_title ||
  201. !IsA(wiki_text_div , 'DIV') || !wiki_text_regex .test(existing_wiki_text ) ||
  202. !IsA(categories_div , 'DIV') || !categories_regex.test(existing_categories) )
  203. return "ERROR: edit post data failed" ;
  204. return '' ;
  205. } , WIKI_TITLE_H1_ID , WIKI_TEXT_DIV_ID , CATEGORIES_DIV_ID ,
  206. page_title , wiki_text , categories_text , IsA , DBG)) ;
  207. if (!err) LOG("successfully updated: " + page_title) ; NextPage() ;
  208. }
  209. /* main loop */
  210. function DefineStep(step_function , should_wait_for_pageload)
  211. {
  212. var step_data = {} ;
  213. step_data[STEP_FUNCTION_KEY] = step_function ;
  214. step_data[PAGELOAD_WAIT_KEY] = should_wait_for_pageload ;
  215. Steps.push(step_data) ;
  216. }
  217. function MainLoop()
  218. {
  219. DBG('MainLoop')() ;
  220. if (!!ShouldQuit) { LOG_ERR(ShouldQuit) ; Done() ; return ; }
  221. else if (!IsReady ) { PumpMainLoop() ; return ; }
  222. clearTimeout(StepTimeout) ;
  223. StepTimeout = setTimeout(function() { ForceQuit("timeout executing step") ; } , STEP_TIMEOUT) ;
  224. var step_data = Steps[StepN++] ;
  225. var step = step_data && step_data[STEP_FUNCTION_KEY] ;
  226. ShouldWait = step_data && step_data[PAGELOAD_WAIT_KEY] ;
  227. IsReady = false ;
  228. if (typeof step !== 'function') Done() ;
  229. else { LOG_STEP(step.name) ; step() ; PumpMainLoop() ; }
  230. }
  231. function PumpMainLoop() { setTimeout(MainLoop , 250) ; }
  232. function ForceQuit(quit_msg) { ShouldQuit = quit_msg || 'script error' ; PumpMainLoop() ; }
  233. function Done() { LOG("done") ; phantom.exit() ; LOG('') ; }
  234. /* helpers */
  235. function GetUrl(get_url)
  236. {
  237. DBG('GetUrl')(get_url) ;
  238. Page.open(get_url , function(status) { if (status != 'success') ForceQuit("status: " + status) ; }) ;
  239. }
  240. function EvalQuitOnErr(result)
  241. {
  242. // CAVEATS: this function is intended to handle the return value of 'Page.evaluate' callbacks
  243. // on success, the 'Page.evaluate' callback must return a string not matching /^ERROR:.*/
  244. // the Step function must set IsReady to true sometime after this function returns
  245. // unless it is waiting for a page load, in which case, 'Page.onLoadFinished' will flag it
  246. if (typeof result != 'string' || !result.indexOf('ERROR:')) ForceQuit(result) ;
  247. else IsReady = !ShouldWait ;
  248. return result ;
  249. }
  250. function NextPage() { StepN = NEXT_PAGE_STEP ; IsReady = true ; }
  251. function IsA(an_object , expected_type)
  252. {
  253. if (!an_object || an_object == [] || an_object == '') return false ;
  254. switch(expected_type)
  255. {
  256. case Array : return Object.prototype.toString.call(an_object) == '[object Array]' ; break ;
  257. case String : return Object.prototype.toString.call(an_object) == '[object String]' ; break ;
  258. case 'BUTTON' :
  259. case 'DIV' :
  260. case 'FORM' :
  261. case 'H1' :
  262. case 'INPUT' :
  263. case 'LI' :
  264. case 'TEXTAREA' : return an_object.nodeName == expected_type ; break ;
  265. default : return false ; break ;
  266. } ;
  267. }
  268. function EditPageUrl(page_title) { return EDIT_URL.replace(TITLE_URL_REGEX , page_title) ; }
  269. /* event hendlers */
  270. Page.onLoadStarted = function() { IsReady = false ; LOG_ARGS.apply("Page.onLoadStarted" , arguments) ; } ;
  271. Page.onLoadFinished = function() { IsReady = true ; LOG_ARGS.apply("Page.onLoadFinished " + Page.url , arguments) ; } ;
  272. Page.onUrlChanged = function() { LOG_ARGS.apply("Page.onUrlChanged" , arguments) ; } ;
  273. /* logging */
  274. Page.onConsoleMessage = function(msg) { DBG('')(msg) ; } ;
  275. var DEBUG = true ; var DEBUG_EVS = DEBUG && false ; var DEBUG_VB = DEBUG && false ;
  276. function LOG (log , color) { console.log((color || '\033[00;32m') + log + '\033[00m' ) ; }
  277. function LOG_STEP(name) { LOG("Step: " + name , '\033[01;32m') ; }
  278. function LOG_ERR (err) { LOG("ERROR: " + err , '\033[00;31m') ; }
  279. function LOG_DBG (dbg) { if (!DEBUG ) return ;
  280. LOG("DEBUG: " + dbg , '\033[00;34m') ; }
  281. function LOG_ARGS() { if (!DEBUG_EVS) return ;
  282. LOG("EVENT: " + this) ; var args = arguments ;
  283. for (arg in args) LOG(" arg: " + JSON.stringify(args[arg])) ; }
  284. function DUMPFILE() { DBG('')("dumping to file: " + Page.url.toLowerCase()) ;
  285. require('fs').write('dump.txt' , Page.content , 'w') ; }
  286. function DBG(context)
  287. {
  288. function Dbg(dbg) { console.log('\033[00;34m' + dbg + '\033[00m') ; }
  289. function DbgSubmitEditEvalIn(wiki_text_input , wiki_submit_button)
  290. {
  291. Dbg("SubmitEdit() wiki_text_input=" + wiki_text_input ) ;
  292. Dbg("SubmitEdit() wiki_text_input .nodeName" + wiki_text_input .nodeName) ;
  293. Dbg("SubmitEdit() wiki_submit_button" + wiki_submit_button ) ;
  294. Dbg("SubmitEdit() wiki_submit_button.nodeName" + wiki_submit_button.nodeName) ; }
  295. function DbgSubmitEditEvalOut(new_wiki_text , existing_wiki_text , modified_wiki_text , post_wiki_text , wiki_text_regex)
  296. { Dbg("SubmitEdit() new_wiki_text=" + new_wiki_text ) ;
  297. Dbg("SubmitEdit() existing_wiki_text=" + existing_wiki_text ) ;
  298. Dbg("SubmitEdit() modified_wiki_text=" + modified_wiki_text ) ;
  299. Dbg("SubmitEdit() post_wiki_text=" + post_wiki_text ) ;
  300. Dbg("SubmitEdit() (modified_wiki_text == existing_wiki_text)=" + (modified_wiki_text == existing_wiki_text)) ;
  301. Dbg("SubmitEdit() wiki_text_regex.test(existing_wiki_text )=" + wiki_text_regex.test(existing_wiki_text) ) ; }
  302. function DbgSubmitEditOut(post_wiki_text) { if (DEBUG_VB) Dbg("SubmitEdit() returned post_wiki_text=" + post_wiki_text) ; }
  303. function DbgVerifyEdit(wiki_title_h1 , wiki_text_div , categories_div , existing_wiki_title , page_title , existing_wiki_text , new_wiki_text , existing_categories , new_categories , wiki_text_regex , categories_regex)
  304. { Dbg("VerifyEdit() wiki_title_h1=" + wiki_title_h1 + " wiki_title_h1 .nodeName=" + wiki_title_h1.nodeName ) ;
  305. Dbg("VerifyEdit() wiki_text_div=" + wiki_text_div + " wiki_text_div .nodeName=" + wiki_text_div.nodeName ) ;
  306. Dbg("VerifyEdit() categories_div=" + categories_div + " categories_div.nodeName=" + categories_div.nodeName) ;
  307. Dbg("VerifyEdit() existing_wiki_title=" + existing_wiki_title ) ;
  308. Dbg("VerifyEdit() page_title=" + page_title ) ;
  309. Dbg("VerifyEdit() existing_wiki_text=" + existing_wiki_text ) ;
  310. Dbg("VerifyEdit() new_wiki_text=" + new_wiki_text ) ;
  311. Dbg("VerifyEdit() existing_categories=" + existing_categories ) ;
  312. Dbg("VerifyEdit() new_categories=" + new_categories ) ;
  313. Dbg("VerifyEdit() (existing_wiki_title == page_title )=" + (existing_wiki_title == page_title) ) ;
  314. Dbg("VerifyEdit() wiki_text_regex .test(existing_wiki_text )=" + wiki_text_regex .test(existing_wiki_text) ) ;
  315. Dbg("VerifyEdit() categories_regex.test(existing_categories)=" + categories_regex.test(existing_categories) ) ; }
  316. function DbgMainLoop() { if (DEBUG_VB) Dbg("MainLoop() ShouldQuit=" + ShouldQuit + " IsReady=" + IsReady) ; }
  317. function DbgGetUrl(get_url) { Dbg("GetUrl() " + get_url) ; }
  318. return eval('Dbg' + context) ;
  319. } if (!DEBUG) DBG = function(context) { return function(){} } ;
  320. /* main entry */
  321. DefineStep(Prepare , false) ;
  322. DefineStep(LoadLoginPage , true ) ;
  323. DefineStep(SubmitLogin , true ) ;
  324. DefineStep(VerifyLogin , false) ;
  325. DefineStep(LoadWikiData , false) ;
  326. DefineStep(LoadEditPage , true ) ;
  327. DefineStep(SubmitEdit , true ) ;
  328. DefineStep(VerifyEdit , false) ;
  329. for (step_n in Steps) if (Steps[step_n][STEP_FUNCTION_KEY].name == 'LoadWikiData') NEXT_PAGE_STEP = step_n ;
  330. MainLoop() ;