Greasy Fork is available in English.

Kissanime Anti-Captcha

Automatically solves captchas on Kissanime.ru

//SHOULD PUT ALL CODE IN ORDER IT SHOULD EXECUTE IN (ie imgclicked goes after setting the event handlers)
	//REALLY NEED TO WORK ON PGRM FLOW ITS PRETTY UNREADABLE
//MIGHT WANT TO MAKE AN OBJECT CALLED solve WHICH HAS img AND words FIELDS TO CLEAN UP THE CODE
//HAVE TO THINK ABOUT WHAT THOSE AWFUL ADS COULD DO TO THIS
	//MIGHT WANT TO TRY TO REMOVE THE ADS AGIAN
//SHOULD PROBABLY USE THE SAME FUNCTION FOR DISPLAYING RECENT AUTOSOLVES AND DISPLAYING ALL THE SOLVES, JUST PASS THE FUNC A DATABASE AND IT'LL SPIT OUT A DIV
//SOMETIMES IMAGES THAT ARE 1 PIXEL SMALLER OR LARGER THEN USUAL ARE DISPLAYED, IDK WHY AND THERE ONLY SEEMS TO BE 2 SIZES FOR ALL THE IMAGES THAT DO HAVE DOUBLES BUT TO FIX IT YOU'D HAVE TO CHECK COLOR RATIOS ON ALL THE IMAGES AND STUFF AND IT WOULDN'T REALLY BE WORTH IT SO FRIG IT
	//EXAMPLES OF DOUBLES OF IMAGES THAT YOU HAVE ARE girl, blue hair (hatsune miku with the leaks) AND dog, brown fur AND turtle, stand
	//IT MIGHT HAVE SOMETHING TO DO WITH ME CHANGING MY ZOOM IN CHROME WHEN I FIRST NOTICED THIS BUT ALMOST NONE OF THE IMAGES ARE DIFFERENT SIZES THEN BEFORE AND THEY DONT SEEM TO CHANGE SIZE WHEN I ZOOM IN/OUT SO IDK


// ==UserScript==
// @name         Kissanime Anti-Captcha
// @namespace    http://tampermonkey.net/
// @version      Sept. 2017
// @description  Automatically solves captchas on Kissanime.ru
// @author       Ehren Julien-Neitzert
// @match        http://kissanime.ru/Special/AreYouHuman*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// ==/UserScript==

(function() {


	function imgToStr(img) {
		canvas = document.createElement('canvas');
		canvas.width = img.width;
		canvas.height = img.height;
		context = canvas.getContext('2d'); //a context is something that will let me actually draw to the canvas, 2d is for images and stuff
		context.drawImage(img, 0, 0, img.width, img.height); //draw the image on the canvas, FOR SOME REASON IF I DONT SPECIFY EXACTLY HOW TALL AND WIDE THEN IT'LL DRAW AN IMAGE THATS TOO BIG OR TOO SMALL
		return canvas.toDataURL("image/png"); //converts img to string which is the png data for the image, to run toDataUrl the img src needs to be not cross origin for whatever reason
	}
	function strToImg(imgStr) {
		var img = document.createElement('img');
		img.src = imgStr;
		return img;
	}

	function wordSpansToStrs(words) {
		trueWords = [];
		for (var i = 0; i < 2; i++) { //get words from spans
			text = words[i].innerText; //get the inner text of the word spans
			texts = text.split(', '); //split the 2 words apart
			trueWords.push(texts);
		}
		return trueWords;
	}

	function setImgOnclicks(images) {
		for (var i = 0; i < images.length; i++) { //set event listener on every image
			images[i].addEventListener('click', imgClicked);
		}
	}
	function imgClicked() { //when a captcha image is clicked
		imgStr = imgToStr(this); //convert the image that called this function to a string
		console.log("Words already on this image: ", GM_getValue(imgStr));
		if (!autoSolves[currentWords]) { //if a person clicked on the captcha image
			console.log("HUMAN CLICK");
			solves.push([imgStr, trueWords[currentWords]]); //add the word/img combo to the solves
			GM_setValue("currentKissanimeSolves", solves); //DO THIS EVERY TIME AN IMAGE IS CLICKED BECAUSE SOMETIMES ONE OF THE 2 WILL BE AUTO SOLVED SO THERE WILL ONLY BE 1 SOLVE, save the solves so that when the next page loads we can check if the solves are good
		}
		console.log("Found Solves: ", solves);
		currentWords++; //go to the next pair of words
		this.hidden = true; //hide this image so it can't be unclicked or reclicked
		words[1].hidden = false; //unhide the 2nd pair of words
		if (autoSolves[currentWords]) { //if the next pair of words are solvable, USING CURRENTWORDS INSTEAD OF JUST OR ELSE WHEN I AUTOCLICK THE 2ND SOLVE IT'LL AUTOCLICK THE 2ND SOLVE REPEADITLY FOREVER, SINCE CURRENTWORDS KEEPS INCREASING EVERY TIME I CLICK AN IMAGE THEN AFTER THE 2ND ONE IT'LL CHECK IF THERES A 3RD ELEMENT IN THE autosolve ARRAY WHICH WILL COME BACK undefined
			autoSolves[currentWords].click(); //click the next image
		}
	}

	function saveSolves() { //fired when captcha page is visited agian
		solved = GM_getValue("currentKissanimeSolves");
		allImgs = GM_getValue("kissanimeAllSolvedImgs");
		if (solved) { //if solved isn't empty
			console.log("Now Saving: ", solved);
			for (s = 0; s < solved.length; s++) { //for every solve
				imgStr = solved[s][0];
				newWords = solved[s][1];
				if (allImgs.indexOf(imgStr) != -1) { //if this img has solves on it
					allWords = GM_getValue(imgStr);
					for (w = 0; w < newWords.length; w++) { //go through the 2 new words
						newWord = newWords[w];
						if (allWords.indexOf(newWord) == -1) { //HAVE TO CHECK IF THE WORD'S ALREADY IN THERE CAUSE IF ONE SOLVE IS "emoticon, beer" AND THE NEXT SOLVE IS "emoticon, drink" THEN IT'LL ADD THE WORD emoticon TO THE IMAGE'S WORDS TWICE!
							allWords.push(newWord); //if its a new word, add it
						}
					}
					GM_setValue(imgStr, allWords);
				} else { //if the captcha does not have any words assosiated with it
					allImgs.push(imgStr);
					GM_setValue("kissanimeAllSolvedImgs", allImgs); //first add the imgStr to the list of all solved images
					GM_setValue(imgStr, newWords); //then create the list of words specifically for the image
				}
			}
		}
	}
	function dumpSolves() { //sets solves to undefined
		solved = GM_getValue("currentKissanimeSolves");
		console.log("Dumping: ", solved);
		GM_setValue("currentKissanimeSolves", false); //reset the current solves, CANT SET THIS TO undefined CAUSE IF I TRY IT SETS IT TO THE STRING "undefined"
	}

	function printAllImgs() {
		allImgs = GM_getValue("kissanimeAllSolvedImgs"); 
		console.log(allImgs);
	}
	function makeDisplayAllSolvesButton() {
		document.body.appendChild(document.createElement('p')); //just for some indentation
		a = document.createElement('a');
		a.innerText = "Click here to display the entire database of solves";
		a.onclick = displayAllSolves;
		a.style = "color:blue; text-decoration:underline;";
		a.id = "databaseOpener";
		document.body.appendChild(a);
	}
	function displayAllSolves() {
		document.body.removeChild(document.getElementById("databaseOpener"));
		allImgs = GM_getValue("kissanimeAllSolvedImgs");
		numOfImgs = allImgs.length;
		for (s = 0; s < numOfImgs; s++) {
			imgStr = allImgs[s];
			var img = strToImg(imgStr);
			document.body.appendChild(img);
			imgWords = GM_getValue(imgStr);
			var p = document.createElement('p');
			innerText = imgWords.join(', '); //jus' like thon but not
			p.innerText = innerText;
			document.body.appendChild(p);
		}
	}
	function displayRecentAutoSolves() {
		latestAutoSolvesBoxDescGen(); //describes the box of auto solves
		console.log("displaying recent auto solves");
		latestAutos = GM_getValue("kissanimeRecentAutoSolves");
		console.log(latestAutos);
		displayBox = document.createElement('div'); //the big box that'll hold everything
		displayBox.style = "border-style: solid; border-color: #000000;"; //give it a border
		numOfAutos = latestAutos.length;
		for (a = 0; a < numOfAutos; a++) { //for all the most recent auto solves
			imgBox = document.createElement('div'); //make a div to put the img and its words in
			imgBox.style = "display:inline-block; margin-top:20px; margin-left:20px; margin-right:20px; margin-bottom:20px"; //so that the next div will go right beside it and theres enough space to look nice
			imgStr = latestAutos[a][0];
			img = strToImg(imgStr); //convert the string to an img
			console.log(img);
			imgBox.appendChild(img); //put the img in the div
			solveWords = latestAutos[a][1];
			for (w = 0; w < solveWords.length; w++) { //for each of the solve words on the image
				p = document.createElement('p'); //make their own element
				p.style = 'text-align: center; margin-top:2px; margin-bottom: 2px;'; //center the paragraph in the div and make sure theres not too much space between words
				p.innerText = solveWords[w]; //put the word in the element
				p.addEventListener('click', removeParagraphSolve); //make it so clicking on the word removes it from solves
				imgBox.appendChild(p); //put the word in the same box as its img
			}
			displayBox.appendChild(imgBox); //put the box in the big box
		}
		document.body.appendChild(displayBox); //add the big box to the DOM
	}
	function removeParagraphSolve() { //function called by a paragraph containing a word that describes an image that's in the same div as the paragraph
		wordBeingRemoved = this.innerText;
		confirmation = confirm("are you sure you'd like to remove " + wordBeingRemoved + " from the image's list of words?");
		if (confirmation) {
			img = this.parentNode.getElementsByTagName('img')[0]; //get the only image in the save div as the text
			imgStr = imgToStr(img);
			allImgWords = GM_getValue(imgStr);
			index = allImgWords.indexOf(wordBeingRemoved);
			if (index != -1) {
				allImgWords.pop(); //take the word out of the list of all assosiated words
				GM_setValue(imgStr, allImgWords);
			} else {
				alert(wordBeingRemoved + " could not be removed because it's not associated with the image. Maybe you already removed it? (to see all the words associated with the image, see the full database below)");
			}
			console.log("all words left on image: ", GM_getValue(imgStr));
		}
	}
	function latestAutoSolvesBoxDescGen() {
		p = document.createElement('p');
		p.innerText = "Below in the black box are the latest automatic solves. If any of the phrases describing the image are incorrect then click on the phrase to remove it from the above image's list of associated phrases.";
		document.body.appendChild(p);
	}

	function deleteFromDatabase(imgStr) { //removes an image and all its solves from the database
		GM_deleteValue(imgStr); //first remove all solve words on the imgStr
		allImgs = GM_getValue("kissanimeAllSolvedImgs"); //then remove the imgStr from the list of all solves
		index = allImgs.indexOf(imgStr);
		allImgs.pop(index);
		GM_setValue("kissanimeAllSolvedImgs", allImgs);
	}
	function removeDuplicateWords() {
		allImgs = GM_getValue("kissanimeAllSolvedImgs");
		len = allImgs.length;
		for (var i = 0; i < len; i++) {
			imgStr = allImgs[i];
			allImgWords = GM_getValue(imgStr);
			newAllImgWords = [];
			numOfWords = allImgWords.length;
			for (w = 0; w < numOfWords; w++) {
				currentWord = allImgWords[w];
				if (allImgWords.indexOf(currentWord) == w) {
					newAllImgWords.push(currentWord);
				}
			}
			GM_setValue(imgStr, newAllImgWords);
		}
	}

	function getImgSolves(images, wordStrs) {
		allImgs = GM_getValue("kissanimeAllSolvedImgs");
		autoSolves = [null, null];
		for (wordPair = 0; wordPair < 2; wordPair++) { //for each pair of words
			for (i = 0; i < images.length; i++) { //for every captcha image
				image = images[i];
				str = imgToStr(image); //convert the image to a string
				if (allImgs.indexOf(str) != -1) { //if there are solves available for the image
					imgWords = GM_getValue(str); //get the solve words
					captchaWords = wordStrs[wordPair]; //the 2 words that must be matched to an image
					pass = true; 
					for (w = 0; w < captchaWords.length; w++) { //for each captcha word (2)
						if (imgWords.indexOf(captchaWords[w]) == -1) { //if this image can't be described by a captcha word, it fails
							pass = false;
						}
					}
					if (pass) { //if the image and captcha words match
						autoSolves[wordPair] = image;
						break; //move on to the next pair of captcha words
					}
				}
			}
		}
		autosWithWords = []; //this is the array im gonna save to use to display the autosolves on the error page
		for (s = 0; s < 2; s++) { //autoSolves will always be 2 long
			if (autoSolves[s]) { //NEED TO MAKE SURE IM NOT TRYING TO CONVERT null TO A STR USING imgToStr
				autosWithWords.push([imgToStr(autoSolves[s]), wordStrs[s]]);
			}
		}
		GM_setValue("kissanimeRecentAutoSolves", autosWithWords); //set this here so I can display the autosolves on the error page
		console.log("Autosolves: ", autoSolves);
		return autoSolves;
	}


	url = window.location.href;

	if (url == "http://kissanime.ru/Special/AreYouHuman2") { //if the recent solves were trash
		dumpSolves(); //just dump the solves
		printAllImgs();
		displayRecentAutoSolves();
		makeDisplayAllSolvesButton();
	}

	else { //if the captcha page is being loaded
		captchaZone = document.getElementsByClassName('barContent')[0]; //class containing the part of the page with the captchas
		words = captchaZone.getElementsByTagName('span'); //contains the spans that contain the words, the only spans in the captchaZone are the descriptive words you have to fufill
		trueWords = wordSpansToStrs(words); //gets the strings from the spans, should contain 2 arrays of 2 strings
		words[1].hidden = true; //REMEMBER THAT WHILE HIDDEN THE INNER TEXT OF A WORD IS COMPLETELY HECKED, hides the 2nd pair of words so that the user clicks on an image matching the first pair
		images = captchaZone.getElementsByTagName('img'); //gets all images in the captchaZone
		currentWords = 0; //keeps track of which pair of words we're on 

		saveSolves(); //save the previous solves (if they were good/existed)
		dumpSolves(); //then dump the solves

		autoSolves = getImgSolves(images, trueWords);
		solves = []; //create new global array to hold the new solves until I can confirm they're both good solves
		setImgOnclicks(images); //set the onclicks
		if (autoSolves[0]) { //if the first captcha can be autoSolved
			autoSolves[0].click();
		}
	}


})();