MediaWiki:Gadget-wikisync-core.js

From Old School Near-Reality Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
  1 /** <nowiki>
  2  * WikiSync:
  3  * - Utilises our WikiSync API, with data provided by users via RuneLite.
  4  * - Adds the ability to check a user's completed quests.
  5  * 
  6  * Slightly adapted from https://runescape.wiki/w/MediaWiki:Gadget-questchecker-core.js
  7  *
  8  * @author Jayden
  9  * @author Andmcadams
 10  * @author Jakesterwars
 11  * @author Cook Me Plox
 12  * @author Lezed1
 13  * @author Haidro
 14  */
 15 
 16 
 17  var QC_ACTIVATOR_CLASS = '.qc-active',
 18  QC_INPUT_CLASS = '.qc-input',
 19  ENDPOINTS = {
 20 	osrs: 'https://sync.runescape.wiki/runelite/player/username/STANDARD',
 21 	shatteredrelics: 'https://sync.runescape.wiki/runelite/player/username/SHATTERED_RELICS_LEAGUE' //use actual url
 22  };
 23 
 24 var questCorrections = {
 25 	// Add corrections for API quest names -> wiki names here.
 26 	// API quest name is the key and the wiki page name is the value.
 27  },
 28 
 29  conf = mw.config.get([
 30 	'wgArticlePath'
 31  ]),
 32  icons = {
 33 	'yes': ' <span class="rs-qc-icon"><img class="qc-complete" src="/images/Yes_check.svg?00000" width="15px" ></span>',
 34 	'no':	' <span class="rs-qc-icon"><img class="qc-not-started" src="/images/X_mark.svg?00000" width="13px" ></span>'
 35  };
 36 
 37 wikisync = {
 38 	/**
 39 	 * Startup method
 40 	 */
 41 	init: function () {
 42 		wikisync.createFields();
 43 	},
 44 
 45 	setCheckboxText: function (text) {
 46 		$(".rs-wikisync-hide-completed .oo-ui-labelElement-label").each(function() {
 47 			$(this).text(text);
 48 		})
 49 	},
 50 
 51 	hideCompletedEntries: function () {
 52 		var selected = wikisync.hideCompletedCheckbox.isSelected();
 53 		if (selected) {
 54 			$(".wikisync-completed").hide();
 55 		}
 56 	},
 57 	
 58 	/**
 59 	 * Creates the input fields for the user's RSN
 60 	 */
 61 	createFields: function () {
 62 		if (rs.hasLocalStorage() === true) {
 63 			$.removeCookie('RSN', { path: '/' }); // remove any existing cookies using jQuery, will return false if it doesn't exist so it's fine
 64 			var name = localStorage.getItem('rsn');
 65 		} else {
 66 			var name = wikisync.getCookie('RSN');
 67 		}
 68 		
 69 		var gamemode = 'osrs';
 70 
 71 		$(QC_INPUT_CLASS).each( function() {
 72 			var input1 = new OO.ui.TextInputWidget( { placeholder: 'Display name', id: 'rs-qc-rsn'} );
 73 
 74 			if (name) {
 75 				// Set input to cookie/localStorage value.
 76 				input1.setValue(name);
 77 			}
 78 			
 79 			var button1 = new OO.ui.ButtonInputWidget( {
 80 				label: new OO.ui.HtmlSnippet(
 81 					'<img src="' + rs.getFileURLCached('RuneLite.png') + '" width=' + '"20px" />' + ' Look up'
 82 				),
 83 				title: 'Look up player data using RuneLite',
 84 				flags: [ 'primary', 'progressive' ],
 85 				classes: ['wikisync-lookup-button']
 86 			} );
 87 
 88 			var leagueOnly = $(this).hasClass('league-only')
 89 			if (leagueOnly) {
 90 				gamemode = 'shatteredrelics';
 91 			}
 92 
 93 			var button1action = function() {
 94 				if (rs.hasLocalStorage() === true) {
 95 					localStorage.setItem('rsn', input1.value); // save in localStorage
 96 				} else {
 97 					wikisync.setCookie('RSN', input1.value, 30); // set a cookie for 30 days
 98 				}
 99 				wikisync.loadData(input1.value, gamemode);
100 			};
101 			button1.on('click', button1action);
102 			input1.on('enter', button1action);
103 			
104 			var helpModalBtn = new OO.ui.ButtonWidget({
105 				label: 'Learn how to enable WikiSync',
106 			});
107 			helpModalBtn.$element.click(function (e) {
108 				window.open('https://oldschool.runescape.wiki/w/RuneScape:WikiSync');
109 			});
110 
111 			var hideCompleted = false;
112 			if (rs.hasLocalStorage()) {
113 				if (localStorage.getItem('wikisync-hide-completed') === 'true') {
114 					hideCompleted = true;
115 				}
116 			}
117 			wikisync.hideCompletedCheckbox = new OO.ui.CheckboxInputWidget({
118 				selected: hideCompleted
119 			});
120 			wikisync.hideCompletedCheckbox.on('change', function () {
121 				var selected = wikisync.hideCompletedCheckbox.isSelected();
122 				if (rs.hasLocalStorage() === true) {
123 					localStorage.setItem('wikisync-hide-completed', selected); // save in localStorage
124 				}
125 				$(".wikisync-completed").toggle(!selected);
126 			});
127 
128 			var fieldset = new OO.ui.FieldsetLayout( {
129 			id: 'rs-qc-form',
130 			} );
131 			
132 			var fieldSetItems = [new OO.ui.FieldLayout(input1, {label:'Username:', align:'inline'})];
133 
134 			fieldSetItems.push(button1);
135 			fieldset.addItems( [
136 				new OO.ui.HorizontalLayout({
137 					items: fieldSetItems
138 				}),
139 				new OO.ui.FieldLayout(
140 					helpModalBtn,
141 					{
142 						label: 'No data found. To use this, enable the WikiSync plugin in RuneLite.',
143 						align: 'inline',
144 						classes: ['rs-wikisync-help']
145 					}
146 				),
147 				new OO.ui.LabelWidget( {
148 					label: 'Missing some data from RuneLite for this page. Please log in to the game to re-sync.',
149 					classes: ['rs-wikisync-missingdata']
150 				} ),
151 				new OO.ui.FieldLayout(
152 					wikisync.hideCompletedCheckbox,
153 					{
154 						label: 'Hide completed',
155 						align: 'inline',
156 						classes: ['rs-wikisync-hide-completed']
157 					}
158 				)
159 			]);
160 			
161 			if ($(this).hasClass('lighttable')) {
162 				// If it's a lighttable, insert the fieldset before the table
163 				fieldset.$element.insertBefore(this);
164 			} else {
165 				// If not, insert it inside the element that has the class
166 				$(this).prepend( fieldset.$element );
167 			}
168 			// Hide all of the help elements to start with
169 			$('.rs-wikisync-help').hide();
170 			$('.rs-wikisync-missingdata').hide();
171 			if ($(".srl-tasks, .music-tracks").length === 0) {
172 				// only show checkbox if it's a table with hide-able tasks
173 				$('.rs-wikisync-hide-completed').hide();
174 			}
175 		} );
176 		
177 		if (name) {
178 			// If there is a saved name, load the data for it.
179 			wikisync.loadData(name, gamemode);
180 		}
181 	},
182 	
183 	/**
184 	 * Updates the status text
185 	 */
186 	updateStatus: function (text) {
187 		mw.notify( text, { tag: 'wikisync' } );
188 	},
189 
190 	/**
191 	 * Sets a cookie
192 	 */
193 	setCookie: function (name, value, days) {
194 		var expires = "";
195 		if (days) {
196 			var date = new Date();
197 			date.setTime(date.getTime() + (days*24*60*60*1000));
198 			expires = "; expires=" + date.toUTCString();
199 		}
200 		document.cookie = name + "=" + (value || "") + expires + "; path=/";
201 	},
202 	
203 	/**
204 	 * Returns the value of a cookie, or null if it doesn't exist
205 	 */
206 	getCookie: function (name) {
207 		var cookie = new RegExp("^(?:.*;)?\\s*" + name + "\\s*=\\s*([^;]+)(?:.*)?$"),
208 			match = document.cookie.match(cookie);
209 		
210 		if (match !== null) {
211 			return match[1];
212 		} else {
213 			return null;
214 		}
215 	},
216 	
217 	/**
218 	 * Load data
219 	 */
220 	loadData: function (rsn, gamemode) {
221 		if (!rsn) {
222 			wikisync.updateStatus("Invalid RSN");
223 			return;
224 		}
225 		
226 		var endpoint = ENDPOINTS[gamemode || 'osrs'] || ENDPOINTS.osrs;
227 		// Hide help text if it is showing.
228 		$('.rs-wikisync-help').hide();
229 		$('.rs-wikisync-missingdata').hide();
230 		$('.wikisync-success').remove();
231 
232 		$.ajax({ // Get the quest data
233 			type: "GET",
234 			url: endpoint.replace("username", rsn),
235 			dataType: "json",
236 			success: function(msg) {
237 				var userQuests = {};
238 				Object.entries(msg.quests).forEach(function(q, ix) {
239 					var k = q[0],
240 						v = q[1];
241 					// Correct quest names to wiki page names
242 					if (k in questCorrections) {
243 						var correctName = questCorrections[k];
244 						userQuests[correctName] = v;
245 					} else {
246 						userQuests[k] = v;
247 					}
248 				});
249 				var userSkills = {};
250 				Object.entries(msg.levels).forEach(function(q, ix) {
251 					var k = q[0],
252 						v = q[1];
253 					userSkills[k] = v;
254 				});
255 				$('.rs-qc-icon').remove();
256 				$(".wikisync-completed").show();
257 				$(".wikisync-completed").removeClass("wikisync-completed");
258 				$('<img>').attr("src","/images/f/fb/Yes_check.svg?00000").addClass('wikisync-success').css('width', '20px').css('height', '20px').css('position', 'relative').insertAfter( '.wikisync-lookup-button' );
259 
260 				const hasAllData = [
261 					wikisync.addQuestIcons(userQuests),
262 					wikisync.addQuestTable(userQuests, userSkills, msg.achievement_diaries),
263 					wikisync.addDiaryTable(msg.achievement_diaries),
264 					wikisync.addSkillTable(userSkills),
265 					wikisync.addSkillIcons(userSkills),
266 					wikisync.addLeagueTasks(msg.league_tasks),
267 					wikisync.addMusicTracks(msg.music_tracks),
268 					wikisync.addCombatAchievementTasks(msg.combat_achievements),
269 				].every(function(result){return result});
270 
271 				if (!hasAllData) {
272 					$('.rs-wikisync-missingdata').show();
273 				}
274 			},
275 			error: function(req) {
276 				$('.rs-qc-icon').remove();
277 				wikisync.setCheckboxText("Hide completed");
278 				if (req.responseJSON && req.responseJSON.code && req.responseJSON.code === 'NO_USER_DATA') {
279 					$('.rs-wikisync-help').show();
280 				} else {
281 					wikisync.updateStatus('There was a problem loading data for ' + rsn);
282 				}
283 			}
284 		});
285 	},
286 
287 	/**
288 	 * Clicks the league task rows
289 	 */
290 	addLeagueTasks: function (tasks) {
291 		var taskTables = $('table.qc-active.srl-tasks');
292 		if (taskTables.length === 0) {
293 			// Nothing to do...
294 			return true;
295 		}
296 
297 		var seen = {};
298 		var total = 0;
299 		var completed = 0;
300 		$(".table-completed").remove();
301 		tasks.forEach(function(taskId) {
302 			seen[taskId] = true;
303 		});
304 		taskTables.each(function() {
305 			var table_total = 0;
306 			var table_completed = 0;
307 			$(this).find('tr[data-taskid]').each(function() {
308 				var taskid = $(this).data("taskid");
309 				if (!!seen[taskid] !== $(this).hasClass("highlight-on")) {
310 					$(this).click();
311 				}
312 				if (seen[taskid]) {
313 					$(this).addClass("wikisync-completed");
314 					table_completed++;
315 					completed++;
316 				}
317 				table_total++;
318 				total++;
319 			})
320 			if (taskTables.length > 1) {
321 				$(this).before($("<span class='table-completed' style='font-style: italic;'>Showing "+(table_total - table_completed)+" of "+table_total+" tasks ("+table_completed+" completed)</span>"));
322 			}
323 		})
324 		wikisync.setCheckboxText("Hide completed (" + completed + "/" + total + " completed)");
325 		wikisync.hideCompletedEntries();
326 		return true;
327 	},
328 	
329 	/**
330      * Clicks the Combat Achievement rows
331      */
332     addCombatAchievementTasks: function (combatAchievements) {
333         var combatAchievementTable = $('table.qc-active.ca-tasks');
334         if (combatAchievementTable.length === 0) {
335             // Page doesn't have Combat Achievement tasks on it
336             return true;
337         }
338 
339         if (combatAchievementTable === null) {
340             return false;
341         }
342 
343         var seen = {};
344         combatAchievements.forEach(function(taskId) {
345             seen[taskId] = true;
346         });
347         combatAchievementTable.each(function() {
348             $(this).find('tr[data-ca-task-id]').each(function() {
349                 var task_id = $(this).data("ca-task-id");
350                 if (!!seen[task_id] !== $(this).hasClass("highlight-on")) {
351                     $(this).click();
352                 }
353                 if (seen[task_id]) {
354                     $(this).addClass("wikisync-completed");
355                 }
356             });
357         });
358 
359         return true;
360     },
361 
362 	/**
363 	 * Clicks the music track rows
364 	 */
365 	addMusicTracks: function (musicTracks) {
366 		var musicTable = $('table.qc-active.music-tracks');
367 		if (musicTable.length === 0) {
368 			// Not a music track page
369 			return true;
370 		}
371 
372 		if (musicTracks === null) {
373 			// Missing data
374 			return false;
375 		}
376 
377 		var seen = {};
378 		var total = 0;
379 		var completed = 0;
380 		musicTracks.forEach(function(trackId) {
381 			seen[trackId] = true;
382 		});
383 		musicTable.each(function() {
384 			$(this).find('tr[data-music-id]').each(function() {
385 				var music_id = $(this).data("music-id");
386 				if (!!seen[music_id] !== $(this).hasClass("highlight-on")) {
387 					$(this).click();
388 				}
389 				if (seen[music_id]) {
390 					$(this).addClass("wikisync-completed");
391 					completed++;
392 				}
393 				total++;
394 			})
395 		})
396 		wikisync.setCheckboxText("Hide unlocked tracks (" + completed + "/" + total + " unlocked)");
397 		wikisync.hideCompletedEntries();
398 		return true;
399 	},
400 
401 	/**
402 	 * Clicks the rows in a table of question and diary tiers. Also appends icons to rows dedicated to skill training
403 	 */
404 	 addQuestTable: function (quests, skills, achievementDiaries) {
405 		// Quest and diary completion
406 		$('table.qc-active.oqg-table tr[data-rowid]').each(function() {
407 			var rowID = $(this).data("rowid");
408 			var isAchievementDiary = rowID.includes("Diary#");
409 			
410 		 if (isAchievementDiary) {
411 			 // Achievement diary rowIDs are formatted as "$NAME Diary#$TIER", where "$NAME" may contain spaces.
412 			 function splitOnLastOccurence(str, splitOn) {
413 				 var index = str.lastIndexOf(splitOn);
414 				 return { before: str.slice(0, index), after: str.slice(index + 1) };
415 			 }
416 			 var diaryName = splitOnLastOccurence(rowID, " ").before;
417 			 var diaryTier = splitOnLastOccurence(rowID, "#").after;
418 			 if (diaryName in achievementDiaries && diaryTier in achievementDiaries[diaryName] && "complete" in achievementDiaries[diaryName][diaryTier]) {
419 				 var diaryCompleted = achievementDiaries[diaryName][diaryTier].complete;
420 				 if (diaryCompleted !== null) {
421 					 if (diaryCompleted !== $(this).hasClass("highlight-on")) {
422 						 $(this).click();
423 					 }
424 				 }
425 			 }
426 		 } else {
427 			 var questName = rowID;
428 			 if (questName in quests) {
429 				 var questCompleted = quests[questName] == 2;
430 				 if (questCompleted !== $(this).hasClass("highlight-on")) {
431 					 $(this).click();
432 				 }
433 			 }
434 		 }
435 		});
436 
437 
438 		// Skill training complete
439 		$('table.qc-active.oqg-table tr[data-skill][data-skill-level]').each(function() {
440 			var skillName = $(this).data("skill");
441 			skillName = skillName.charAt(0).toUpperCase() + skillName.slice(1);
442 			var skillLevel = $(this).data("skill-level");
443 			var row = $(this).find('th')
444 			if (skills[skillName] >= skillLevel) {
445 				row.append(icons.yes);
446 			} else {
447 				row.append(icons.no);
448 			}
449 		});
450 		return true;
451 	},
452 
453 	// Clicks cells/rows in a table based on skill levels.
454 	addSkillTable: function(skills) {
455 		$('table.qc-active.skill-table [data-skill][data-skill-level]').each(function() {
456 			var skillName = $(this).data("skill");
457 			var skillLevel = $(this).data("skill-level");
458 			var skillCompleted = skills[skillName] >= skillLevel;
459 			if (skillCompleted !== $(this).hasClass("highlight-on")) {
460 				$(this).click();
461 			}
462 		});
463 		return true;
464 	},
465 
466 	// Clicks rows in a table based on achievement diary task completion
467 	addDiaryTable: function(achievementDiaries) {
468 		var hasAllData = true;
469 		$('table.qc-active.diary-table[data-diary-name][data-diary-tier]').each(function() {
470 			var task_index = -1;
471 			var diaryName = $(this).data("diary-name");
472 			var diaryTier = $(this).data("diary-tier");
473 			$(this).find('tr').each(function() {
474 				if (task_index < 0) {
475 					task_index += 1;
476 					return;
477 				}
478 				if (diaryName in achievementDiaries && diaryTier in achievementDiaries[diaryName] && achievementDiaries[diaryName][diaryTier].tasks.length > task_index) {
479 					var task_completed = achievementDiaries[diaryName][diaryTier].tasks[task_index];
480 
481 					if (task_completed !== null) {
482 						if (task_completed !== $(this).hasClass("highlight-on")) {
483 							$(this).click();
484 						}
485 					} else {
486 						hasAllData = false;
487 					}
488 				}
489 				task_index += 1;
490 			});
491 		});
492 		return hasAllData;
493 	},
494 
495 	/**
496 	 * Adds the icons next to respective quests
497 	 */
498 	addQuestIcons: function (quests) {
499 		$(QC_ACTIVATOR_CLASS + " a").each(function() {
500 			if ($(this).html().toLowerCase() != "expand" || $(this).html().toLowerCase() != "collapse") {
501 				var questTitle = $(this).text().trim(),
502 					icon = $(this).find('.rs-qc-icon'),
503 					imgsrc = '';
504 				if (quests[questTitle] === 2) {
505 					$(this).append(icons.yes);
506 				}
507 				if (quests[questTitle] === 0 || quests[questTitle] === 1) {
508 					$(this).append(icons.no);
509 				}
510 			}
511 		});
512 		return true;
513 	},
514 
515 	/**
516 	 * Adds the icons next to respective skills
517 	 */
518 	addSkillIcons: function (userLevels) {
519 		$(QC_ACTIVATOR_CLASS + " .scp").each(function() {
520 			var level = $(this).data('level');
521 			var skill = $(this).data('skill');
522 			if (typeof(level) !== 'number' || userLevels[skill] === undefined) {
523 				return;
524 			}
525 			if (userLevels[skill] >= level) {
526 				$(this).append(icons.yes);
527 			} else {
528 				$(this).append(icons.no);
529 			}
530 		});
531 		return true;
532 	}
533  };
534 
535 $(wikisync.init);