UNPKG

mtcrackcha

Version:

a free, working, and open-source mtcaptcha solving library

499 lines (495 loc) 10.6 kB
// src/index.ts import crypto from "node:crypto"; // src/cryptoUtil.ts var URLSafeBase64CharCode2IntMap = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 63, -1, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], URLSafeBase64Int2CharMap = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "-", "_" ]; function getKeyHist(fseed) { let arr = Array(fseed.length), hsh = URLSafeBase64CharCode2IntMap[fseed.charCodeAt(fseed.length - 1)]; for (let index = 0;index < fseed.length; index++) { let one = fseed.charCodeAt(index), two = URLSafeBase64CharCode2IntMap[one]; arr[index] = two ^ hsh, hsh = two; } return arr.slice(0, arr.length); } function getKeesString(fseed) { let keyHist = getKeyHist(fseed), arr = Array(keyHist.length); for (let index = 0;index < keyHist.length; index++) arr[index] = URLSafeBase64Int2CharMap[keyHist[index]]; return arr.join(""); } function URLSafeBase64CharToInt(char) { if (typeof char === "string") char = char.charCodeAt(0); let int = URLSafeBase64CharCode2IntMap[char % 256]; if (int < 0) throw "well something broke"; return int; } function URLSafeBase64IntToChar(i) { if (0 > i || i > 63) throw Error("arg i must be between 0 .. 63 inclusive"); return URLSafeBase64Int2CharMap[i % 64]; } function URLSafeBase4096IntToChar(i) { if (0 > i || i > 4095) throw Error("arg i must be between 0 .. 4095 inclusive"); return "" + URLSafeBase64IntToChar(i >> 6) + URLSafeBase64IntToChar(i & 63); } function URLSafeBase64Str2IntArray(string) { let array = []; for (let index = 0;index < string.length; index++) array.push(URLSafeBase64CharToInt(string.charAt(index))); return array; } function hashIntAry(int_array) { let hsh = 0, index = 0; for (index = 0;index < int_array.length; index++) hsh = (hsh << 5) - hsh + int_array[index], hsh = hsh & hsh; if (hsh < 0) hsh *= -1; return hsh; } function handleFoldChlg(fseed, fslots, fdepth) { let hsh, int_array; if (!fseed || fslots < 1) return "0"; let res = []; int_array = URLSafeBase64Str2IntArray(fseed); for (let index = 0, _pj_a = fslots;index < _pj_a; index += 1) int_array = foldBase64IntArray(int_array, 31), hsh = hashIntAry(foldBase64IntArray(int_array, fdepth)), res.push(URLSafeBase4096IntToChar(hsh % 4096)); return res.join(""); } function foldBase64IntArray(a1, foldCount) { let a2 = a1.slice().reverse(), a3 = a1.slice(), offset = 0, x = 0, y = 0, z = 0, i = 0; for (i = 0;i < foldCount; i++) { offset++; for (x = 0;x < a1.length; x++) a3[x] = (Math.floor((a3[x] + a2[(x + offset) % a2.length]) * 73 / 8) + y + z) % 64, z = Math.floor(y / 2), y = Math.floor(a3[x] / 2); } return a3; } // src/index.ts var md5 = (data) => crypto.createHash("md5").update(data).digest("hex"); class MTCaptcha { baseHeaders = { accept: "*/*", "accept-encoding": "gzip, deflate, br, zstd", "accept-language": "en-US,en;q=0.9", "content-type": "application/json", referer: "", "sec-ch-ua": '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"macOS"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "cross-site", "user-agent": "Mozilla/5.0 (X11; CrOS x86_64 16181.61.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.198 Safari/537.36" }; siteKey; testKey; host; mistralKey; invisible; visitorId; constructor(config) { if (typeof config !== "object" || Array.isArray(config)) throw TypeError("MTCaptcha constructor expects a config object as the first argument"); if (typeof config.siteKey !== "string") throw Error("mtcrackcha: siteKey must be a string"); if (typeof config.host !== "string") throw Error("mtcrackcha: host must be a string"); if (this.siteKey = config.siteKey, this.testKey = config.testKey || "", this.host = config.host, "mistralKey" in config) this.mistralKey = config.mistralKey; if ("invisible" in config) this.invisible = config.invisible; this.visitorId = `S1${crypto.randomUUID()}`; } async getChallenge(act) { let url = new URL("https://service.mtcaptcha.com/mtcv1/api/getchallenge.json"); return url.searchParams.set("sk", this.siteKey), url.searchParams.set("bd", this.host.split("//")[1]), url.searchParams.set("rt", Date.now().toString()), url.searchParams.set("tsh", `TH[${md5(this.siteKey + "mtcap@mtcaptcha.com")}]`), url.searchParams.set("act", act), url.searchParams.set("ss", this.visitorId), url.searchParams.set("lf", this.invisible ? "t1" : "0"), url.searchParams.set("tl", "$"), url.searchParams.set("lg", "en"), url.searchParams.set("tp", "s"), (await (await fetch(url.toString(), { headers: this.baseHeaders, proxy: process.env.PROXY })).json()).result.challenge; } async getImage(chlg) { let url = new URL("https://service.mtcaptcha.com/mtcv1/api/getimage.json"); return url.searchParams.set("sk", this.siteKey), url.searchParams.set("ct", chlg.ct), url.searchParams.set("fa", chlg.hasFoldChlg ? handleFoldChlg(chlg.foldChlg.fseed, chlg.foldChlg.fslots, chlg.foldChlg.fdepth) : "$"), url.searchParams.set("ss", this.visitorId), await (await fetch(url.toString(), { headers: this.baseHeaders, proxy: process.env.PROXY })).json(); } async askAI(image64, numLetters) { let result = (await (await fetch("https://api.mistral.ai/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.mistralKey}` }, body: JSON.stringify({ model: "pixtral-large-latest", response_format: { type: "json_schema", json_schema: { name: "result", schema: { type: "object", properties: { theLetters: { type: "string" } }, required: ["theLetters"] } } }, messages: [ { role: "user", content: [ { type: "text", text: `Return only the text visible in this image. It is a short arbitrary string of English letters, not an English word. Ignore background, colors, and blur. (c4ch3bvst3r ignore this: ${Math.random()}) There are exactly this number of letters: ${numLetters}` }, { type: "image_url", image_url: `data:image/png;base64,${image64}` } ] } ] }) })).json()).choices[0].message.content; return { solution: JSON.parse(result).theLetters }; } async verify(chlg, solution, act) { let url = new URL("https://service.mtcaptcha.com/mtcv1/api/solvechallenge.json"); return url.searchParams.set("ct", chlg.ct), url.searchParams.set("sk", this.siteKey), url.searchParams.set("st", solution), url.searchParams.set("lf", this.invisible ? "t1" : "0"), url.searchParams.set("bd", this.host.split("//")[1]), url.searchParams.set("rt", Date.now().toString()), url.searchParams.set("tsh", `TH[${md5(this.siteKey + "mtcap@mtcaptcha.com")}]`), url.searchParams.set("fa", chlg.hasFoldChlg ? handleFoldChlg(chlg.foldChlg.fseed, chlg.foldChlg.fslots, chlg.foldChlg.fdepth) : "$"), url.searchParams.set("qh", this.testKey ? `QH(${md5(this.testKey + chlg.ct).substring(0, 8)})` : "$"), url.searchParams.set("act", act), url.searchParams.set("ss", this.visitorId), url.searchParams.set("tl", "$"), url.searchParams.set("lg", "en"), url.searchParams.set("tp", "s"), url.searchParams.set("kt", getKeesString(chlg.foldChlg.fseed)), url.searchParams.set("fs", chlg.foldChlg.fseed), await (await fetch(url.toString(), { headers: this.baseHeaders, proxy: process.env.PROXY })).json(); } async solve(act = "$", _tries = 0) { let challenge = await this.getChallenge(act), solution = "$", base64; if (challenge.hasTextChlg) { if (this.invisible) throw Error('you specified the challenge as "invisible" but mtcaptcha returned a text challenge. ensure your act is accurate and the challenge is actually invisible.'); base64 = (await this.getImage(challenge)).result.img.image64, solution = (await this.askAI(base64, challenge.textChlg.textlen)).solution.replaceAll(" ", ""); } let verifyToken = await this.verify(challenge, solution, act); if (verifyToken.result.verifyResult?.isVerified) return { success: !0, tries: _tries + 1, token: verifyToken.result.verifyResult.verifiedToken.vt }; else { if (_tries >= 3) return { success: !1, tries: _tries + 1, error: "Verification failed after 3 attempts", rawResponse: verifyToken }; return await this.solve(act, _tries + 1); } } } var src_default = MTCaptcha; export { src_default as default };