MediaWiki:Gadget-wikisync-core.js
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);