User

User:Disgustedorite/SaganBot.js

From Sagan 4 Alpha Wiki

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.
/** JSaganBot version Attercop written by [[User:Disgustedorite]]
 * Special thanks to Joeytje50's JS Wiki Browser and AnguaNatalia's Sagan 4 Wiki Parser, which I referenced / will refrence when making this.
 * 
 * Please keep in mind that while I am attempting to replicate some of the functions of AN's code, 
 * hers was written in php and was designed to speed up wiki editing rather than automate it, 
 * and likewise this is not and could never be an exact replica of the original Sagan 4 Wiki Parser.
 * Therefore, please direct any issues you have using this script to me, not to her.
 */
 
 //Custom commands start here and may be used from the console anywhere
 const api = new mw.Api();
 
 const nonBiomes = [
 	"MICRO",
 	"MACRO",
 	"Micro",
 	"Macro",
 	"Microscopic",
 	"Macroscopic",
 	"Water Tables",
 	"Mixed Scrub",
 	"Twilight Zone",
 	"Midnight Zone",
 	"Larvae",
 	"Adult",
 	"Adults",
 	"Larva",
 	"Spores",
 	"Seeds",
 	"Eggs"
 	
 ];//these, which may be found mid-biome, are explicitly not considered biomes and should be skipped.
 
 const canontheticals = [
 	"Sagan 4",
	"Mason",
	"Photic",
	"Nonphotic",
	"Troposphere",
	"Stratosphere",
	"Global",
	"Sunlight Zone",
	"Twilight Zone",
	"Midnight Zone",
	"Abyssal Zone"
 ];//canon parentheticals to be ignored while parsing biomes
 
 const JSaganBot = {
 	getCompendium(id) {
 		//returns html contents of https://forum.sagan4.org/index.php?act=Print&client=printer&t=1470 (replacing 1470 with the specified ID)
 		//currently non-functional for security reasons
 		fetch("https://forum.sagan4.org/index.php?act=Print&client=printer&t="+id)
 			.then(response => {
 				if (!response.ok) {
 					console.error("Could not obtain compendium");
 				}
 				return response.text();
 			});
 	},
 	
 	convertEcoPage(grouplink) {
 		var topreturn = "";
 		if (grouplink) {
 			topreturn = "|"+grouplink;
 		}
 		
 		
 		
 		let isEditor = document.getElementById("wpPreview");
 		var modifiedcontent = "";
 		
 		if (isEditor == null) {
 			console.error("You must have the editor open to use this.");
 		} else {
 			//console.log("This page has an editor. However, the function is not yet...functional. hehe");
 			var pageContents = document.getElementById("wpTextbox1").value;
 			var pageLines = pageContents.split("\n");
 			
 			var currentBiome = null;
 			var insideList = false;
 			
 			pageLines.forEach((line,index) => {
 				if (currentBiome == null) {
 					//This will only be the case at the beginning of the article. Basically, this exists to skip until it gets to a biome's subheading. I may want to do something here later
 				} else {
 					//detect if the line is a species, and act accordingly
 					if (line.match(/^'*\[\[[^\:\|\(\)\[\]\=]+\]\]\s*\(*[^\|\(\)\[\]\=]*\)*'*.*$/)) {
 						//do expected calculations for species here
 						let speciesLink = line.match(/\[\[([^\(\)\[\]\=]+)\]\]/)[1];
 						
 						let parenthetical = "";
 						
 						if (line.match(/\]\]\s*\(*([^\|\(\)\[\]\=]+)*\)/) != null) {
 							parenthetical = " ("+line.match(/\]\]\s*\(*([^\|\(\)\[\]\=]+)*\)/)[1]+")";
 						}
 						
 						
 						line = "|"+speciesLink+parenthetical;
 						
 						if (insideList == false) {
 							//begin list
 							line = "{{#invoke:EcoData|main|biome="+currentBiome+"\n\n"+line;
 							insideList = true;
 						}
 					} else if (line.match(/^\s*$/) == null && insideList == true) {
 						console.log("Not a species line! Contents: "+line);
 						line = "}}{{topreturn"+topreturn+"}}\n\n"+line;
 						insideList = false;
 					}
 				}
 				if (line.match(/[\=\[][^\|\[]*[\|\[\=]+([^\|\[\]]+)\]*\=+/) != null && nonBiomes.includes(line.match(/[\|\[\=]+([^\|\[\]\=]+)\]*\=+/)[1]) == false){
 					//Valid biome detected! It skips anything with one word except groups. I could write an exception, but biomes like Reef only existed, like, very briefly very early on, so...
 					//if (line.match("|")) {
 					//	currentBiome = line.match(/\=\=\[\[[\w\s\-\d\(\)]\|([^\]]+])/)[1];
 					//} else {
 					//	
 					//}
 					
 					currentBiome = line.match(/[\|\[\=]+([^\|\[\]\=]+)\]*\=+/)[1];
 					console.log("Biome detected! It is called: "+currentBiome);
 				}
 				modifiedcontent = modifiedcontent + "\n" + line;
 			});
 			if (insideList == true) {
 				//ends the last list if it's still open
 				modifiedcontent = modifiedcontent + "}}{{topreturn"+topreturn+"}}";
 				insideList == false;
 			}
 			if (modifiedcontent != "") {
 				document.getElementById("wpTextbox1").value = modifiedcontent;
 				console.log('Conversion complete, but I might have made mistakes. Please double check my work with the "show changes" button!');
 			} else {
 				console.error("Failed to convert page. Please refresh the page to undo my work.");
 			}
 			
 			
 		}
 		
 		
 	},
 	addToEcoPage(species,biomes,week) {
 		console.warn("The automatic ecosystem page updater has not been implemented yet!");
 		console.log("When it is, though, it will take a species and put it exactly where it goes on the ecosystem page.");
 		//No params: runs through entire current generation
 		//Species param: Only does the single species
 		//Biomes param: A string containing a comma-separated list bypasses the species' own biome list
 		//Week param: add to a specific period's ecosystem page
 		
 		var errorstatus = false;
 		var speciesList;
 		var inputGen = 167;
 		var thisWeek = 27;
 		var isSpeciesGotten = false;
 		var isEcoPageGotten = false;
 		var subpageList;
 		
 		if (week) {
 			thisWeek = number(week);
 		}
 		
 		if (species && typeof species !== "number") {
 			speciesList = species.split(", ");
 			console.log("Species list: ",speciesList);
 			isSpeciesGotten = true;
 		} else {
 			//get a list of species from the current generation page
 			if (species && isNaN(number(species)) == false) {
 				inputGen = number(species);
 			}
 		}
 		
 		const ecoPromise = new Promise((resolve, reject) => {api.get({
 			action: "query",
 			format: "json",
 			list: "prefixsearch",
 			pslimit: 20,
 			pssearch: "Week "+thisWeek+"/Ecosystem/" //normal use
 			//pssearch: "User:Disgustedorite/sandbox/AutoEcoTest/" //testing override
 		}).fail(function(error) {
 			console.error("Failed to get ecosystem subpages: ",error);
 			errorstatus = true;
 			reject("Failure");
 		}).done(function(data) {
 			var ecoSubpages = data.query.prefixsearch;
 			if (ecoSubpages) {
 				subpageList = ecoSubpages.map(obj => obj.title);
 				console.log("Ecosystem subpages detected! They are: ",subpageList);
 			} else {
 				console.log("Ecosystem page had no subpages. Now counting itself as the sole subpage instead.");
 				subpageList = [ "Week "+thisWeek+"/Ecosystem" ];
 			}
 			isEcoPageGotten = true;
 			resolve("Success");	
 		});
 		
 		});
 		
 		
 		if (species && typeof species !== "number") {
 			speciesList = species.split(", ");
 			console.log("Species list: ",speciesList);
 			isSpeciesGotten = true;
 		} else {
 			//get a list of species from the current generation page
 			if (species && isNaN(number(species)) == false) {
 				inputGen = number(species);
 			}
 		}
 			
 		const speciesPromise = new Promise((resolve, reject) => {api.get({
            action: 'query',
            titles: "Generation "+inputGen,
            prop: 'revisions',
			rvprop: 'content',
            format: "json",
        }).fail(function(error) {
        	if (isSpeciesGotten == false) {
        		console.error("Could not obtain generation page: ", error);
        		errorstatus = true;
        		reject("Failure");
        	} else {
        		resolve("Success");
        	}
        }).done(function(data) {
        	if (isSpeciesGotten == false) {
        		console.log("Successfully obtained generation page.");
        		let pageId = Object.keys(data.query.pages)[0];
        		let stringData = data.query.pages[pageId].revisions[0]['*'];
        		//console.log("Page parsed! Its contents are: ",stringData);
	    	  
    		    //get a list of species based on contents of the generation page
        		speciesList = stringData.match(/\.\w*\|\[\[([^\|\:\[\]]*)\]\]/g); //makes an array of every species listed on the generation page
        		speciesList = speciesList.map(speciesLine => speciesLine.replace(/\.\w*\|\[\[(.*?)\]\]/g, '$1'));
        		console.log("Species list: ",speciesList);
        		isSpeciesGotten = true;
        	}
        	resolve("Success");
        	
        });
        
        
        	
        });
       
 		
 		Promise.all([ecoPromise,speciesPromise]).then(([ecoResult, speciesResult]) => {
 			if (errorstatus == true) {
 				console.error("Could not complete task: ecosystem subpages, species list, or both could not be obtained");
 			} else {
 				console.log("Species list and ecosystem subpages both successfully obtained");
 				
 				
 				
 				//Get ecosystem subpage content
 				const subpageContentPromises = [];
 				var subpageStatuses = {};
 				var subpageContents = {};
 				subpageList.forEach(pagename => {
 					subpageContentPromises.push(new Promise((resolve, reject) => {api.get({
    			        	action: 'query',
    			        	titles: pagename,
				        	prop: 'revisions',
							rvprop: 'content',
							format: "json",
			    		}).fail(function(error) {
			    			subpageStatuses[pagename] = false;
			    			
			    			reject(pagename +" failed");
			    		}).done(function(data) {
			    			
			    			subpageStatuses[pagename] = true;
			    			let pageId = Object.keys(data.query.pages)[0];
        					let stringData = data.query.pages[pageId].revisions[0]['*'];
        					
        					subpageContents[pagename] = stringData.split("\n");
        					
        					resolve(pagename + " succeeded");
			        	});
 						
 					}));
 				});
 				
 				Promise.all(subpageContentPromises)
 				.then(results => {
 					console.log("Finished getting ecosystem page content: ",subpageStatuses);
 					
 					//now take each species, get its size and biomes, and get to work!!
 					
 					var speciesPages = {};
 					var speciesDataPromises = [];
 					
 					speciesList.forEach((species,index) => {
 						//get species content, the promise is to evade collision (these should not be asynchronous!)
 						let speciesPromise =  new Promise((resolve, reject) => {api.get({
    			       		action: 'query',
    			       		titles: species,
				       		prop: 'revisions',
							rvprop: 'content',
							format: "json",
			    		}).fail(function(error) {
			    			//console.error("Failed to get page content for species ",species);
			    			reject("Failure");
			    		}).done(function(data) {
			    			let pageId = Object.keys(data.query.pages)[0];
			    			//console.log(pageId);
			    			if (pageId != -1) {
			    				let stringData = data.query.pages[pageId].revisions[0]['*'];
			    				var speciesText = stringData;
			    				speciesPages[species] = speciesText;
			    				resolve("Success");
			    			} else {
			    				//console.error("Failed to get page content for species ",species);
			    				reject("Failure");
			    			}
        					
			    		});});
			    		
			    		speciesDataPromises.push(speciesPromise);
			    		
			    		speciesPromise.then((result) => {
			    			//console.log("Getting species data for "+species+" was a ",result);
			    		}).catch((result => {
			    			console.error("Getting species data for "+species+" was a failure.");
			    		}));
 					});
 					Promise.allSettled(speciesDataPromises).then(results => {
 						console.log("Species and their pages are as follows: ",speciesPages);
 						
 						//NOW we get this show on a road
 						for (const [species, page] of Object.entries(speciesPages)) {
 							if (page.match(/\{\{radiation|RemoteSpecies/)) {
 								console.warn("Compound entries are not supported. You will need to add "+species+" to the ecosystem page manually.");
 								continue; //prevents further execution for the compound entry
 							}
 							let habitatList = page.match(/\|habitat\s*\=\s*([^\=\n\|\}]*)\n+[\|\}]/)[1];
 							let size = page.match(/\|size\s*\=\s*([^\=\n\|\}]*)\n+[\|\}]/)[1]; //get size to determine 1) if it's micro or macro 2) what genus size class it goes under
 							
 							if (/\|\s*habitat2\s*[^\s\n\|\}]/.test(habitatList) == true) {
 								console.warn(species+" is using the deprecated habitat2 parameter! Please convert it to the modern format, as some of its biomes will be missed.");
 							}
 							
 							let micromacro;
 							let sizeclass;
 							
 							if (size.match(/\scm|\sm(\;|\s|$)|\skm|\scentimeter|\smeter|\skilometer/)) {
 								micromacro = "macro";
 							} else {
 								micromacro = "micro";
 							}
 							
 							switch (true) {
 								case /(^|\s|\-)([2-9][1-9]|[3-9]\d)(\.\d+)?\s(cm|centimeters?)|\d\d\d\s(cm|centimeters?)|\d\s(m|meters?)(\;|\s|$)/i.test(size):
 									sizeclass = ">20 centimeters";
 									break;
 								case /(^|\s|\-)\d\d(\.\d+)?\s(cm|centimeters?)/i.test(size):
 									sizeclass = "10+ centimeters";
 									break;
 								case /(^|\s|\-)\d(\.\d+)?\s(cm|centimeters?)/i.test(size):
 									sizeclass = "1+ centimeters";
 									break;
 								case /(^|\s|\-)\d(\.\d+)?\s(mm|millimeters?)/i.test(size):
 									sizeclass = "1+ millimeters";
 									break;
 								case /(^|\s|\-)\d\d\d(\.\d+)?\s(\xb5m|micrometers?|um)/i.test(size):
 									sizeclass = "100+ micrometers";
 									break;
 								case /(^|\s|\-)\d\d(\.\d+)?\s(\xb5m|micrometers?|um)/i.test(size):
 									sizeclass = "10+ micrometers";
 									break;
 								case /(^|\s|\-)[1-9](\.\d+)?\s(\xb5m|micrometers?|um)/i.test(size):
 									sizeclass = "1+ micrometers";
 									break;
 								case /(^|\s|\-)0\.\d\s(\xb5m|micrometers?|um)/i.test(size):
 									sizeclass = "<1 micrometer";
 									break;
 								default:
 									sizeclass = "unknown";
 							}
 							
 							//console.log(species+" is considered "+micromacro+"scopic and is of the size class "+sizeclass+".\nIts size value was read as: "+size);
 							
 							//parse biome list
 							if (habitatList.match(/^.*:/) == null) {
 								//console.log(species+" had a habitat list which did not start with a category. It was: "+habitatList);
 								habitatList = "Placeholder: "+habitatList;
 							}
 							//console.log("test1");
 							let habitatArray = [];
 							if (habitatList.match(";")) {
 								habitatArray = habitatList.split(/\;\s*/);
 							} else {
 								habitatArray = [habitatList];
 							}
 							//console.log("test2 also here's an array",habitatArray);
 							let habitatTable = {};
 							
 							habitatArray.forEach(category => {
 								//console.log("is this silently erroring or what");
 								let mainModifier = category.match(/^(\w*):/)[1];
 								//console.log("is the error here?");
 								let biomeSublistString = category.match(/:\s*(.+)$/)[1];
 								//console.log("maybe here? also biomeSublistString is equal to",biomeSublistString);
 								let biomeSublistArray = biomeSublistString.split(/\]*\,\s\[*/);
 								
 								biomeSublistArray = biomeSublistArray.map(function(value) {
 									let subresult = value.match(/\[*([^\[\]]*)\]*(.+)$/);
 									let result = "";
 									if (subresult[2]) {
 										result = subresult[1]+subresult[2];
 									} else {
 										result = subresult[1];
 									}
 									return result;
 								});
 								//console.log("Biome sublist of "+species+":",biomeSublistArray);
 								habitatTable[mainModifier] = biomeSublistArray;
 							});
 							
 							let newHabitatTable = {};
 							
 							for (const [modifier, biome] of Object.entries(habitatTable)) {
 								//Detect parenthetical modifiers
 								
 								let totalModifiers = "";
 								if (modifier != "Placeholder") {
 									totalModifiers = modifier;
 								}
 								
 								biome.forEach(biomename => {
 									let splitup = biomename.match(/^\[*([^\[\]]+)\s\(([^\(\)]*)\)$/);
 									
 									if (splitup != null) {
 										if (canontheticals.includes(splitup[2])) {
 											newHabitatTable[biomename] = totalModifiers;
 										} else if (totalModifiers == "") {
 											newHabitatTable[splitup[1]] = splitup[2];
 										} else {
 											newHabitatTable[splitup[1]] = totalModifiers+", "+splitup[2];
 										}
 									} else {
 										newHabitatTable[biomename.match(/^\[*([^\[\]]+)/)[1]] = totalModifiers;
 									}
 								});
 								
 								
 							}
 							
 							//console.log("Biomes with their modifiers for "+species+":",newHabitatTable);
 							
 							//do ecosystem page check
 							let invalidBiomes = [];
 							let validBiomes = [];
 							let addedTo = [];
 							let alreadyIn = [];
 							//let pagesOfBiomes = {};
 							for (const [biome,modifier] of Object.entries(newHabitatTable)) {
 								//subpageContents remember when I generated THAt a zillion lines ago?
 								//well that's because I wanted to get all api calls that did not need to be repeated out of the way before the loop
 								let editedBiomeName = "";
 								//To do: add code for getting weird names
 								let escapedbiome = biome.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
 								let biomeregex = new RegExp("^[^\\w\\s]*"+escapedbiome+"[^\\w\\s]*$","i");
 								//console.log(escapedbiome,biomeregex);
 								for (const [subpage,content] of Object.entries(subpageContents)) {
 									//console.log(content);
 									if (content.some(e => biomeregex.test(e)) == false && invalidBiomes.includes(biome) == false && validBiomes.includes(biome) == false) {
 										//console.warn("invalid biome detected");
 										invalidBiomes.push(biome);
 									} else if (validBiomes.includes(biome) == true) {
 										invalidBiomes = invalidBiomes.filter(item => item !== biome);
 									} else if (content.some(e => biomeregex.test(e)) == true) {
 										invalidBiomes = invalidBiomes.filter(item => item !== biome);
 										validBiomes.push(biome);
 										//pagesOfBiomes[biome] = subpage; //so I don't have to do more code later LOL
 										
 										//or I could just. do all that code I wanna do in here...
 										//find whole content of biome and check if species is already there
 										let modstring = "";
 										if (modifier != "") {
 											modstring = " ("+modifier+")";
 										}
 										let biomedex = content.findIndex(e => biomeregex.test(e));
 										let numberofequalsigns = content[biomedex].match(/^(=+)[^=]/)[1].length;
 										let chop = content.slice(biomedex + 1);
 										let endbiomedex = chop.findIndex(e => e.match(/=\[\[|noinclude/));
 										endbiomedex = endbiomedex + biomedex;
 										//^, Finds an equal or higher heading, or the end of the string. It does the former using...the number of equal signs. this feels stupid
 										let speciesdex = chop.indexOf("|"+species+modstring);
 										//console.log("Speciesdex:",speciesdex);
 										if (endbiomedex != -1 && biomedex != -1) {
 											if (speciesdex + biomedex < endbiomedex && speciesdex + biomedex > biomedex && speciesdex != -1) {
 												//console.log(species+" is already present in "+biome+" ✓");
 												//console.log("Biome index:",biomedex);
 												//console.log("Species index:",speciesdex + biomedex);
 												//console.log(content[speciesdex+biomedex+1]);
 												//console.log("End of biome index:",endbiomedex);
 												alreadyIn.push(biome);
 											} else {
 												
 												let suitableSection = false; //so I can skip unsuitable sections until a suitable one is found
 												//let suitableSearchType = "init";
 												let groupskip = false;
 												let newbiome = false;
 												let microfactor = false;
 												for (var index = 0; index < content.length; index++) {
 													let line = content[index];
 													//Start from the index of the line the biome was on and go down the page until the appropriate location for the species is found
 													let spotfound = false;
 													if (index >= biomedex && index <= endbiomedex) {
 														
 														if (suitableSection == false) {
 															//attempt to determine if a suitable section has been discovered
 															switch (true) {
 																case /NONE/.test(line):
 																	newbiome = true;
 																	//suitableSection = true;
 																	//let regionName = subpage.match(/\/.*$/)[0];
 																	//line = "{{#invoke:EcoData|main|biome="+biome+"\n\n|"+species+modstring+"\n\n}}{{topreturn|"+regionName+"}}";
 																	//spotfound = true;
 																	break;
 																case /groups/i.test(line):
 																	groupskip = true;
 																	break;
 																case /1 Micrometer/.test(line):
 																	if (sizeclass == "1+ micrometers") {
 																		suitableSection = true;
 																	}
 																	break;
 																case /10 Micrometer/.test(line):
 																	if (sizeclass == "10+ micrometers") {
 																		suitableSection = true;
 																	}
 																	break;
 																case /100 Micrometer/.test(line):
 																	if (sizeclass == "100+ micrometers") {
 																		suitableSection = true;
 																	}
 																	break;
 																case /1 Millimeter|1000 Micrometer/.test(line):
 																	if (sizeclass == "1+ millimeters") {
 																		suitableSection = true;
 																	}
 																	break;
 																case /1 Centimeter|10 Millimeter/.test(line):
 																	if (sizeclass == "1+ centimeters") {
 																		suitableSection = true;
 																	}
 																	break;
 																case /10 Centimeter/.test(line):
 																	if (sizeclass == "10+ centimeters") {
 																		suitableSection = true;
 																	}
 																	break;
 																case /20 Centimeter/.test(line):
 																	if (sizeclass == ">20 centimeters") {
 																		suitableSection = true;
 																	}
 																	break;
 																case /micro/i.test(line):
 																	//Micro section
 																	if (groupskip == false) {
 																		microfactor = true;
 																	}
 																	if (micromacro == "micro" && groupskip == false) {
 																		suitableSection = true;
 																	} else if (micromacro == "micro" && groupskip == true) {
 																		if (sizeclass == "<1 micrometer") {
 																			suitableSection = true;
 																		}
 																	}
 																	break;
 																case /macro/i.test(line):
 																	//Macro section
 																	if (micromacro == "macro" && groupskip == false) {
 																		suitableSection = true;
 																	}
 																	break;
 																case /invoke\:EcoData/i.test(line):
 																	if (microfactor == false && groupskip == false) {
 																		suitableSection = true;
 																	}
 																	break;
 																default:
 																	//no suitability found
 															}
 															
 															if (newbiome == true) {
 																let regionName = subpage.match(/\/.*$/)[0];
 																suitableSection = true;
 																line = "{{#invoke:EcoData|main|biome="+biome+"\n\n|"+species+modstring+"\n\n}}{{topreturn|"+regionName+"}}";
 																spotfound = true;
 																break;
 															}
 															
 														} else {
 															
 															//If it's in a suitable section, find a suitable line
 															// /^\|/.test(line) will be true if it's a species
 															if (/^\|/.test(line) == true) {
 																//see if the species is in the original list, and if its index is higher than that of the current species
 																let foundspecies = line.match(/^\|([^\(\)])(\s\(.*\))?/)[1];
 																if (speciesList.includes(foundspecies)) {
 																	if (speciesList.indexOf(species) < speciesList.indexOf(foundspecies)) {
 																		//Suitable spot found! Add species and end list
 																		content[index] = "|"+species+modstring+"\n\n"+line;
 																		spotfound = true;
 																	}
 																}
 															} else if (/\}\}/.test(line)) {
 																//list ended, so append the species to it
 																content[index] = "|"+species+modstring+"\n\n"+line;
 																spotfound = true;
 															}
 														}
 														if (spotfound == true) {
 															//modify page content and break out
 															let tempdata = "";
 															content.forEach((line2,index2) => {
 																tempdata = tempdata+"\n"+line2;
 															});
 															tempdata = tempdata.replace(/^\n*/,"");
 															subpageContents[subpage] = tempdata.split("\n");
 															addedTo.push(biome);
 															break;
 														}
 														
 													} else if (index > endbiomedex) {
 														if (spotfound == false) {
 															console.error("Could not find a suitable location to insert "+species+" into "+biome+". Debug data: ",sizeclass,micromacro);
 														}
 														
 														break;
 													}
 												}
 												
 												//add the species to the suitable spot.
 												//addedTo.push(biome);
 											}
 										} else {
 											console.error("Something broke with the indexes while checking if "+species+" is in "+biome+".");
 										}
 									} 
 								}
 							}
 							if (alreadyIn.length > 0) {
 								console.log(species+" was already present in: ",alreadyIn);
 							}
 							if (addedTo.length > 0) {
 								console.log(species+" was added to: ",addedTo);
 							}
 							
 							
 							if (invalidBiomes.length !== 0) {
 								console.warn("Some biomes not found for "+species+":",invalidBiomes);
 							} //else {
 								//console.log("No invalid biomes found for "+species+" ✓");
 							//}
 							
 							
 							
 							
 							
 						}//this is the end bracket of the loop
 						console.log("Modified subpage contents:",subpageContents);
 						
 						//save all pages
 						for (const [subpage,content] of Object.entries(subpageContents)) {
 							let contentMerged = "";
 							content.forEach(line => {
 								contentMerged = contentMerged + "\n" + line;
 							});
 							contentMerged = contentMerged.replace(/^\n*/, "");
 							api.postWithToken('csrf', {
								headers: {
    								'Content-Type': 'multipart/form-data'
    							},
 								action: "edit",
 								title: subpage,
 								text: contentMerged,
 								summary: "Added species using JSaganBot",
 								format: 'json'
 							}).done(function(data){
 								console.log("Saved "+subpage+" successfully");
 								
 							}).fail(function(error){
 								console.error(error);
 								
 							});
 							
 						}
 						
 					}).catch(error => {
 						console.warn("There was an error: ",error);
 					});
 					
 				});
 			}
 		});
 		
 		
 		
 	}
 	
 };
 
 //if (mw.config.get('wgCanonicalNamespace')+':'+mw.config.get('wgTitle') === 'Project:JSaganBot/Script' && mw.config.get('wgAction') == 'view')
 //undecided if I'll stick to console scripts or make a page, I'm more comfortable with the console so I'll probably make it console-based and add a fancy gui later
	
	
	console.log("JSaganBot version Attercop successfully initialized");
	console.warn("JSaganBot is a work in progress. Be prepared to scramble to undo an edit that did something you didn't want.");
Cookies help us deliver our services. By using our services, you agree to our use of cookies.