MediaWiki:Gadget-questchecker-core.js

From [N8]
Revision as of 03:21, 24 March 2021 by Banri (talk | contribs) (Created page with "/** <nowiki> * Adds the ability to check a user's quests and skills against requirements * on quest pages. * * Adapted from https://github.com/MidasLamb/RS-Wiki-Quest-Che...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/** <nowiki>
 * Adds the ability to check a user's quests and skills against requirements
 * on quest pages.
 * 
 * Adapted from https://github.com/MidasLamb/RS-Wiki-Quest-Checker/
 * 
 * This gadget is utilised on the following templates/modules/pages:
 * - Module:QuestDetails
 * - Template:Infobox achievement
 * - Quests/Strategy
 * - List of quests
 * - List of quests by age
 *
 * @author JaydenKieran
 */

var ACTIVATOR_CLASS = '.qc-active',
	LIGHTTABLE_CLASS = '.lighttable',
	LIGHTTABLE_RESET_CLASS = '.ht-reset';

var questCorrections = {
	// first value is RuneMetrics name, second is correct (wiki) name
	'Recipe for Disaster: Freeing the Goblin Generals': 'Recipe for Disaster: Freeing the Goblin generals',
	'Recipe for Disaster: Freeing the Mountain Dwarf': 'Recipe for Disaster: Freeing the Mountain dwarf',
	// first value is RuneMetrics name, second is correct (wiki) name without the suffix (miniquest) or (saga)
	'A Guild of Our Own (miniquest)':'A Guild of Our Own',
	'Bar Crawl (miniquest)':'Bar Crawl',
	'Benedict\'s World Tour (miniquest)':'Benedict\'s World Tour',
	'Boric\'s Task I (miniquest)':'Boric\'s Task I',
	'Boric\'s Task II (miniquest)':'Boric\'s Task II',
	'Boric\'s Task III (miniquest)':'Boric\'s Task III',
	'Damage Control (miniquest)':'Damage Control',
	'Desert Slayer Dungeon (miniquest)':'Desert Slayer Dungeon',
	'Doric\'s Task I (miniquest)':'Doric\'s Task I',
	'Doric\'s Task II (miniquest)':'Doric\'s Task II',
	'Doric\'s Task III (miniquest)':'Doric\'s Task III',
	'Doric\'s Task IV (miniquest)':'Doric\'s Task IV',
	'Doric\'s Task V (miniquest)':'Doric\'s Task V',
	'Doric\'s Task VI (miniquest)':'Doric\'s Task VI',
	'Doric\'s Task VII (miniquest)':'Doric\'s Task VII',
	'Doric\'s Task VIII (miniquest)':'Doric\'s Task VIII',
	'Enter the Abyss (miniquest)': 'Enter the Abyss',
	'Eye for an Eye (miniquest)':'Eye for an Eye',
	'Final Destination (miniquest)':'Final Destination',
	'Flag Fall (miniquest)':'Flag Fall',
	'Foreshadowing (miniquest)':'Foreshadowing',
	'From Tiny Acorns (miniquest)':'From Tiny Acorns',
	'Ghosts from the Past (miniquest)':'Ghosts from the Past',
	'Harbinger (miniquest)':'Harbinger',
	'Head of the Family (miniquest)':'Head of the Family',
	'Hopespear\'s Will (miniquest)':'Hopespear\'s Will',
	'In Memory of the Myreque (miniquest)':'In Memory of the Myreque',
	'Jed Hunter (miniquest)':'Jed Hunter',
	'Koschei\'s Troubles (miniquest)':'Koschei\'s Troubles',
	'Lair of Tarn Razorlor (miniquest)':'Lair of Tarn Razorlor',
	'Lost Her Marbles (miniquest)':'Lost Her Marbles',
	'Mahjarrat Memories (miniquest)':'Mahjarrat Memories',
	'One Foot in the Grave (miniquest)':'One Foot in the Grave',
	'Purple Cat (miniquest)':'Purple Cat',
	'Raksha, the Shadow Colossus (miniquest)':'Raksha, the Shadow Colossus',
	'Rebuilding Edgeville (miniquest)':'Rebuilding Edgeville',
	'Sheep Shearer (miniquest)':'Sheep Shearer',
	'Sins of the Father (miniquest)':'Sins of the Father',
	'Spiritual Enlightenment (miniquest)':'Spiritual Enlightenment',
	'Tales of Nomad (miniquest)':'Tales of Nomad',
	'Tales of the God Wars (miniquest)':'Tales of the God Wars',
	'The Curse of Zaros (miniquest)':'The Curse of Zaros',
	'The General\'s Shadow (miniquest)':'The General\'s Shadow',
	'The Hunt for Surok (miniquest)':'The Hunt for Surok',
	'The Lost Toys (miniquest)':'The Lost Toys',
	'Tortle Combat (miniquest)':'Tortle Combat',
	'Tuai Leit\'s Own (miniquest)':'Tuai Leit\'s Own',
	'Wandering Ga\'al (miniquest)':'Wandering Ga\'al',
	'Witch\'s Potion (miniquest)':'Witch\'s Potion',
	'Nadir (saga)':'Nadir',
	'Thok It To \'Em (saga)':'Thok It To \'Em',
	'Thok Your Block Off (saga)':'Thok Your Block Off',
	'Three\'s Company (saga)':'Three\'s Company',
	'The Vault of Shadows (miniquest)':'The Vault of Shadows',
	'Vengeance (saga)':'Vengeance',
},
	skills = ["Attack","Defence","Strength","Constitution","Ranged","Prayer",
				"Magic","Cooking","Woodcutting","Fletching","Fishing","Firemaking",
				"Crafting","Smithing","Mining","Herblore","Agility","Thieving","Slayer",
				"Farming","Runecrafting","Hunter","Construction","Summoning",
				"Dungeoneering","Divination","Invention","Archaeology"], // Used to turn skill ID's into usable names.
	
	conf = mw.config.get([
        'wgArticlePath'
    ]),

	icons = {
		'yes': '/images/f/fb/Yes_check.svg',
		'no':  '/images/a/a2/X_mark.svg',
	},

    self = {
        /**
         * Startup method
         */
        init: function () {
            self.createFields();
        },
        
        /**
         * Resets the lighttables
         */
        resetLighttable: function () {
        	$(ACTIVATOR_CLASS).each(function(index) {
        		if ($(this).hasClass('lighttable')) {
        			var reset = $(this).find(LIGHTTABLE_RESET_CLASS)
        			if (reset.length) {
        				// This fires the reset click handler specified in Gadget-highlighttable.js
        				// It's really messy, but there's no other way to do this
        				reset.trigger('click')
        			}
        		}
        	})
        },
        
        /**
         * Creates the input fields on the quest page for the user's RSN
         */
        createFields: function () {
        	if (rs.hasLocalStorage() === true) {
        		$.removeCookie('RSN', { path: '/' }); // remove any existing cookies using jQuery, will return false if it doesn't exist so it's fine
        		var name = localStorage.getItem('rsn');
        	} else {
        		var name = self.getCookie('RSN');
        	}
        	
        	$(ACTIVATOR_CLASS).each( function() {
                var input1 = new OO.ui.TextInputWidget( { placeholder: 'Display name', id: 'rs-qc-rsn'} );
                
                if (name) { // set input to cookie/localStorage value
                    input1.setValue(name);
                    self.loadData(name);
                }
                
                var button1 = new OO.ui.ButtonInputWidget( {
                      label: 'Look up',
                      flags: [ 'primary', 'progressive' ]
                } );
                button1.on('click', function() {
                    if (rs.hasLocalStorage() === true) {
                        localStorage.setItem('rsn', input1.value); // save in localStorage
                    } else {
                        self.setCookie('RSN', input1.value, 30); // set a cookie for 30 days
                    }
                    self.loadData(input1.value);
                });

                var fieldset = new OO.ui.FieldsetLayout( { 
                  id: 'rs-qc-form',
                } );

                fieldset.addItems( [ 
                  new OO.ui.ActionFieldLayout(
                      input1,
                      button1
                  )
                ] );
                
                if ($(this).hasClass('lighttable')) {
                    // If it's a lighttable, insert the fieldset before the table
                    fieldset.$element.insertBefore(this)
                } else {
                    // If not, insert it inside the element that has the class
                    $(this).prepend( fieldset.$element );
                }
        	} );
        },
        
        /**
         * Updates the status text
         */
        updateStatus: function (text) {
        	mw.notify( text, { tag: 'questchecker' } );
        },

		/**
		 * Sets a cookie
		 */
		setCookie: function (name, value, days) {
		    var expires = "";
		    if (days) {
		        var date = new Date();
		        date.setTime(date.getTime() + (days*24*60*60*1000));
		        expires = "; expires=" + date.toUTCString();
		    }
		    document.cookie = name + "=" + (value || "")  + expires + "; path=/";
		},
        
        /**
         * Returns the value of a cookie, or null if it doesn't exist
         */
        getCookie: function (name) {
        	var cookie = new RegExp("^(?:.*;)?\\s*" + name + "\\s*=\\s*([^;]+)(?:.*)?$"),
        		match = document.cookie.match(cookie);
        	
        	if (match !== null) {
        		return match[1];
        	} else {
        		return null;
        	}
        },
        
        /**
         * Load data
         */
         loadData: function (rsn) {
         	self.resetLighttable();
         	
         	if (!rsn) {
         		self.updateStatus("Invalid RSN");
         		return;
         	}
         	
         	self.updateStatus('Loading data for ' + rsn);
         	
		    $.ajax({ // Get the quest data
		        type: "GET",
		        url: "/cors/m=runemetrics/quests?user=" + rsn,
		        dataType: "json",
		        success: function(msg) {
		            if (msg.quests.length === 0) {
		            	self.updateStatus("Error loading data. The account's RuneMetrics profile may be private.");
		                console.error("Could not fetch quest data");
		            } else {
		                var userQuests = {};
		                var userQuestPoints = 0;
		                msg.quests.forEach(function(item, index) {
		                	// Calculate the user's quest points
		                	if (item.status == 'COMPLETED') {
		                		// If quest is completed, add it to the user's total quest points
		                		userQuestPoints = userQuestPoints + item.questPoints;
		                	}
		                	
		                	// Correct quest names to wiki page names
		                    if (item.title in questCorrections) {
		                        var correctName = questCorrections[item.title];
		                        userQuests[correctName] = item.status;
		                    } else {
		                    	userQuests[item.title] = item.status;
		                    }
		                });
		                self.addQuestIcons(userQuests);
		                self.addQuestPoints(userQuestPoints);
		            }
		        }
		    });
		    
		    $.ajax({ // Get the skill data.
		        type: "GET",
		        url: "/cors/m=runemetrics/profile/profile?user=" + rsn + "&activities=0",
		        dataType: "json",
		        success: function(msg) {
		            if ("error" in msg) {
		            	self.updateStatus("Error loading data. The account's RuneMetrics profile may be private.");
		                console.error("Could not fetch skills data");
		            } else {
		                var userLevels = {};
		                msg.skillvalues.forEach(function(item, index){
		                    userLevels[skills[item.id]] = item.level; 
		                });
		                self.addSkillIcons(userLevels);
		            }
		        }
		    });
		    
		    self.updateStatus('Quest and skills data loaded for ' + rsn);
         },
         
         /**
          * Adds icons next to respective quest points
          */
         addQuestPoints: function (questPoints) {
         	var qpRegex = /([0-9]+)\squest point/gi;
         	$(ACTIVATOR_CLASS + " li:not(:has(>ul))").each(function(index) {
         		var icon = $(this).find('.rs-qc-icon')
         		var matches = qpRegex.exec($(this).text())
         		if (matches && matches.length) {
         			// This is a list element in the format of "[x]" quest points
         			// Now we get the first capture group, which contains the number
         			var requiredPoints = matches[1]
         			if (requiredPoints > questPoints) {
         				// Does not have the required points
		            	if (icon.length !== 0) {
		            		var existingImg = icon.eq(0).find('img').eq(0);
		            		existingImg.attr('src', icons.no);
		            		existingImg.attr('title', 'You have ' + questPoints + ' quest points.')
		            	} else {
		            		$(this).append(' <span class="rs-qc-icon"><img class="qc-not-started" src="' + icons.no + '" width="13px" title="You have ' + questPoints + ' quest points." ></span> ');
		            	}
         			} else if (requiredPoints <= questPoints) {
         				// Has the required points
		            	if (icon.length !== 0) {
		            		var existingImg = icon.eq(0).find('img').eq(0);
		            		existingImg.attr('src', icons.yes);
		            		existingImg.attr('title', 'You have ' + questPoints + ' quest points.')
		            	} else {
         					$(this).append(' <span class="rs-qc-icon"><img class="qc-complete" src="' + icons.yes + '" width="15px" title="You have ' + questPoints + ' quest points." ></span> ');
		            	}
         			}
         		}
         	});
         },
         
         /**
          * Adds the icons next to respective quests
          */
         addQuestIcons: function (quests) {
		    $(ACTIVATOR_CLASS + " a").each(function(index) {
		        if ($(this).html().toLowerCase() != "expand" || $(this).html().toLowerCase() != "collapse") {
		            var questTitle = $(this).text().trim(),
		            	icon = $(this).find('.rs-qc-icon'),
		            	imgsrc = '';
		            if ($(this).parents('table' + LIGHTTABLE_CLASS).length) {
		            	/* We are in a highlight table, so we need to handle things differently
		            	   Because highlight tables have no exposed API, we have to trigger the click handler
		            	   that Gadget-highlighttable.js adds to the rows in order to highlight
		            	   and set the data accordingly. It's messy, but it will probably work */
		            	var trs = $(this).parents('tr')
		            	if (trs.length) {
		            		var parent = trs[0]
		            		if (quests[questTitle] == "COMPLETED") {
		            			$(parent).trigger('click')
		            		}
		            	}
		            } else {
			            if (quests[questTitle] == "COMPLETED") {
			            	if (icon.length !== 0) {
			            		icon.eq(0).find('img').eq(0).attr('src', icons.yes );
			            	} else {
			                	$(this).append(' <span class="rs-qc-icon"><img class="qc-complete" src="' + icons.yes + '" width="15px" ></span>');
			            	}
			            }
			            if (quests[questTitle] == "NOT_STARTED") {
			            	if (icon.length !== 0) {
			            		icon.eq(0).find('img').eq(0).attr('src', icons.no );
			            	} else {
			                	$(this).append(' <span class="rs-qc-icon"><img class="qc-not-started" src="' + icons.no + '" width="13px" ></span>');
			            	}
			            }
			            if (quests[questTitle] == "STARTED") {
			            	if (icon.length !== 0) {
			            		icon.eq(0).find('img').eq(0).attr('src', icons.started );
			            	} else {
			                	$(this).append(' <span class="rs-qc-icon"><span class="qc-in-progress"></span></span>');
			            	}
			            }
		            }
		        }
		    });
         },

         /**
          * Adds the icons next to respective skills
          */
         addSkillIcons: function (userLevels) {
		    $(ACTIVATOR_CLASS + " > ul > li").each(function(index) {
		        var textArr = $(this).text().split(" "),
		        	level = textArr[0],
		        	imgsrc = "";
		        if (isNaN(level)) { //Check if it is a number.
		            return;
		        }

		        var skillElement = $(this).find("a").filter(function(index) {
		            // Find the link element which has a skill name.
		            return ($(this).text().trim() in userLevels);
		        });
		        
		        skillElement.each(function(index) {
			        var skill = $(this).text().trim(),
			        	icon = $(this).find('.rs-qc-icon');
			        
			        if (level !== "" && skill !== ""){
			            if (userLevels[skill] >= level) {
			            	if (icon.length !== 0) {
			            		icon.eq(0).find('img').eq(0).attr('src', icons.yes );
			            	} else {
			                	skillElement.append(' <span class="rs-qc-icon"><img src="' + icons.yes + '" width="15px" ></span>');
			            	}
			            } else {
			            	if (icon.length !== 0) {
			            		icon.eq(0).find('img').eq(0).attr('src', icons.no );
			            	} else {
			                	skillElement.append(' <span class="rs-qc-icon"><img src="' + icons.no + '" width="15px" ></span>');
			            	}
			            }
			        }
		        })
		    });
         }
    };

$(self.init);

// </nowiki>