Universal Math CAPTCHA Solver

Solves ALL basic math CAPTCHAs (+, -, *, /) in any format

  1. // ==UserScript==
  2. // @name Universal Math CAPTCHA Solver
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.0
  5. // @description Solves ALL basic math CAPTCHAs (+, -, *, /) in any format
  6. // @author ChrisN40
  7. // @match *://*/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const config = {
  15. checkInterval: 1000,
  16. maxAttempts: 3,
  17. retryDelay: 1000,
  18. mathPatterns: [
  19. // Standard equation formats
  20. /(?:solve|calculate|what is|answer)\s*(?:the )?(?:following )?(?:math )?(?:problem|function|question)?[:]?\s*([\d+\-*\/=. ]+)/i,
  21. /([\d]+\s*[\+\-\*\/]\s*[\d]+)/,
  22.  
  23. // Word problem formats
  24. /first number is (\d+)[^\d]+(\d+)[^\d]+([\+\-\*\/])/i,
  25. /sum of (\d+) and (\d+)/i,
  26. /add (\d+) (?:and|to) (\d+)/i,
  27. /subtract (\d+) (?:from|and) (\d+)/i,
  28. /product of (\d+) and (\d+)/i,
  29. /multiply (\d+) (?:and|by) (\d+)/i,
  30. /divide (\d+) (?:by|and) (\d+)/i,
  31. /(\d+) plus (\d+)/i,
  32. /(\d+) minus (\d+)/i,
  33. /(\d+) times (\d+)/i,
  34. /(\d+) multiplied by (\d+)/i,
  35. /(\d+) divided by (\d+)/i,
  36.  
  37. // Special cases
  38. /how much is (\d+) ([\+\-\*\/]) (\d+)/i
  39. ]
  40. };
  41.  
  42. class UniversalMathSolver {
  43. constructor() {
  44. this.attempts = new WeakMap();
  45. this.init();
  46. }
  47.  
  48. init() {
  49. console.log('Universal Math Solver initialized');
  50. this.setupMutationObserver();
  51. this.checkMathCaptchas();
  52. setInterval(() => this.checkMathCaptchas(), config.checkInterval);
  53. }
  54.  
  55. setupMutationObserver() {
  56. const observer = new MutationObserver(mutations => {
  57. mutations.forEach(mutation => {
  58. mutation.addedNodes.forEach(node => {
  59. if (node.nodeType === 1 && this.isMathCaptcha(node.textContent)) {
  60. this.processMathElement(node);
  61. }
  62. });
  63. });
  64. });
  65.  
  66. observer.observe(document.body, {
  67. childList: true,
  68. subtree: true,
  69. characterData: true
  70. });
  71. }
  72.  
  73. checkMathCaptchas() {
  74. const textElements = document.querySelectorAll('div, p, span, label, td, li, b, strong, h1, h2, h3, h4, h5, h6');
  75.  
  76. textElements.forEach(element => {
  77. if (this.shouldProcessElement(element)) {
  78. this.processMathElement(element);
  79. }
  80. });
  81. }
  82.  
  83. shouldProcessElement(element) {
  84. if (!this.attempts.has(element)) {
  85. this.attempts.set(element, 0);
  86. }
  87. return this.attempts.get(element) < config.maxAttempts &&
  88. this.isMathCaptcha(element.textContent);
  89. }
  90.  
  91. isMathCaptcha(text) {
  92. if (!text) return false;
  93. return config.mathPatterns.some(pattern => pattern.test(text));
  94. }
  95.  
  96. processMathElement(element) {
  97. const attempts = this.attempts.get(element);
  98. this.attempts.set(element, attempts + 1);
  99.  
  100. try {
  101. const mathProblem = this.extractMathProblem(element.textContent);
  102. if (mathProblem) {
  103. const solution = this.solveMathProblem(mathProblem);
  104. if (solution !== null) {
  105. this.fillSolution(solution, element);
  106. }
  107. }
  108. } catch (error) {
  109. console.error('Math CAPTCHA solve error:', error);
  110. if (attempts < config.maxAttempts - 1) {
  111. setTimeout(() => this.processMathElement(element), config.retryDelay);
  112. }
  113. }
  114. }
  115.  
  116. extractMathProblem(text) {
  117. for (const pattern of config.mathPatterns) {
  118. const match = text.match(pattern);
  119. if (match) {
  120. // Handle word problems
  121. if (match[1] && match[2] && match[3]) {
  122. const num1 = parseFloat(match[1]);
  123. const num2 = parseFloat(match[2]);
  124. const operator = this.normalizeOperator(match[3]);
  125.  
  126. // Handle subtraction word order ("subtract A from B" = B - A)
  127. if (match[0].includes('subtract') && operator === '-') {
  128. return `${num2}${operator}${num1}`;
  129. }
  130. return `${num1}${operator}${num2}`;
  131. }
  132. // Handle standard equations
  133. else if (match[1]) {
  134. return match[1].replace(/\s/g, '');
  135. }
  136. }
  137. }
  138. return null;
  139. }
  140.  
  141. normalizeOperator(op) {
  142. const opMap = {
  143. 'plus': '+',
  144. 'add': '+',
  145. 'sum': '+',
  146. 'minus': '-',
  147. 'subtract': '-',
  148. 'times': '*',
  149. 'multiplied': '*',
  150. 'product': '*',
  151. 'divide': '/',
  152. 'divided': '/'
  153. };
  154. return opMap[op.toLowerCase()] || op;
  155. }
  156.  
  157. solveMathProblem(problem) {
  158. try {
  159. // Clean and validate the expression
  160. const cleanExpr = problem.replace(/[^\d+\-*\/.]/g, '');
  161. if (!/^[\d+\-*\/.]+$/.test(cleanExpr)) return null;
  162.  
  163. // Split into tokens (numbers and operators)
  164. const tokens = cleanExpr.split(/([\+\-\*\/])/).filter(x => x);
  165.  
  166. // Convert to numbers and operators
  167. const elements = [];
  168. for (const token of tokens) {
  169. if (['+', '-', '*', '/'].includes(token)) {
  170. elements.push({ type: 'operator', value: token });
  171. } else {
  172. elements.push({ type: 'number', value: parseFloat(token) });
  173. }
  174. }
  175.  
  176. // First pass: multiplication and division
  177. for (let i = 0; i < elements.length; i++) {
  178. const el = elements[i];
  179. if (el.type === 'operator' && (el.value === '*' || el.value === '/')) {
  180. const left = elements[i-1].value;
  181. const right = elements[i+1].value;
  182. let result;
  183.  
  184. if (el.value === '*') {
  185. result = left * right;
  186. } else {
  187. if (right === 0) return '∞'; // Division by zero
  188. result = left / right;
  189. }
  190.  
  191. // Replace the three elements (left, op, right) with result
  192. elements.splice(i-1, 3, { type: 'number', value: result });
  193. i -= 2; // Adjust index after replacement
  194. }
  195. }
  196.  
  197. // Second pass: addition and subtraction
  198. let result = elements[0].value;
  199. for (let i = 1; i < elements.length; i += 2) {
  200. const operator = elements[i].value;
  201. const number = elements[i+1].value;
  202.  
  203. if (operator === '+') {
  204. result += number;
  205. } else {
  206. result -= number;
  207. }
  208. }
  209.  
  210. // Round to 2 decimal places if needed
  211. return result % 1 === 0 ? result : parseFloat(result.toFixed(2));
  212. } catch (e) {
  213. console.error('Math solving failed:', e);
  214. return null;
  215. }
  216. }
  217.  
  218. fillSolution(solution, referenceElement) {
  219. const input = this.findInputField(referenceElement);
  220. if (!input) return false;
  221.  
  222. // Try different methods to set the value
  223. const methods = [
  224. () => { input.value = solution; return input.value == solution; },
  225. () => {
  226. input.focus();
  227. input.value = solution;
  228. this.triggerEvents(input);
  229. return input.value == solution;
  230. },
  231. () => {
  232. const setter = Object.getOwnPropertyDescriptor(
  233. HTMLInputElement.prototype, 'value').set;
  234. setter.call(input, solution);
  235. this.triggerEvents(input);
  236. return input.value == solution;
  237. }
  238. ];
  239.  
  240. for (const method of methods) {
  241. try {
  242. if (method()) {
  243. console.log(`Solved: ${solution}`);
  244. return true;
  245. }
  246. } catch (e) {
  247. console.debug('Method failed:', e);
  248. }
  249. }
  250. return false;
  251. }
  252.  
  253. findInputField(referenceElement) {
  254. const selectors = [
  255. 'input[type="text"]',
  256. 'input[type="number"]',
  257. 'input:not([type])',
  258. 'textarea',
  259. '#answer',
  260. '#solution',
  261. '#captcha',
  262. '#mathAnswer'
  263. ];
  264.  
  265. // Check in parent containers
  266. let container = referenceElement;
  267. while (container) {
  268. for (const selector of selectors) {
  269. const input = container.querySelector(selector);
  270. if (input) return input;
  271. }
  272. container = container.parentElement;
  273. }
  274.  
  275. // Check nearby elements
  276. const allInputs = document.querySelectorAll('input, textarea');
  277. const refRect = referenceElement.getBoundingClientRect();
  278. let closestInput = null;
  279. let minDistance = Infinity;
  280.  
  281. allInputs.forEach(input => {
  282. const inputRect = input.getBoundingClientRect();
  283. const distance = Math.sqrt(
  284. Math.pow(inputRect.left - refRect.left, 2) +
  285. Math.pow(inputRect.top - refRect.top, 2)
  286. );
  287. if (distance < minDistance) {
  288. minDistance = distance;
  289. closestInput = input;
  290. }
  291. });
  292.  
  293. return closestInput;
  294. }
  295.  
  296. triggerEvents(input) {
  297. ['focus', 'keydown', 'keypress', 'keyup', 'input', 'change', 'blur'].forEach(event => {
  298. input.dispatchEvent(new Event(event, {
  299. bubbles: true,
  300. cancelable: true
  301. }));
  302. });
  303. }
  304. }
  305.  
  306. // Start the solver
  307. new UniversalMathSolver();
  308. })();