mtcrackcha
Version:
a free, working, and open-source mtcaptcha solving library
499 lines (495 loc) • 10.6 kB
JavaScript
// 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
};