├── src
├── data
│ ├── key.txt
│ ├── proxy.txt
│ ├── users.txt
│ ├── log.error.txt
│ ├── device.txt
│ ├── token.json
│ └── phone.json
├── helpers
│ ├── game2.wasm
│ ├── delay.js
│ ├── log.js
│ ├── token.js
│ ├── datetime.js
│ ├── generator.js
│ ├── file.js
│ └── payload-blum.js
├── services
│ ├── server.js
│ ├── device.js
│ ├── invite.js
│ ├── daily.js
│ ├── fake.js
│ ├── farming.js
│ ├── user.js
│ ├── tribe.js
│ ├── key.js
│ ├── http.js
│ ├── auth.js
│ ├── game.js
│ └── task.js
├── lang
│ ├── zh.json
│ ├── en.json
│ ├── vi.json
│ ├── id.json
│ └── ru.json
└── run
│ └── index.js
├── package.json
├── README.md
└── yarn.lock
/src/data/key.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/data/proxy.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/data/users.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/data/log.error.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/data/device.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/data/token.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/src/helpers/game2.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/candyburst/blum/HEAD/src/helpers/game2.wasm
--------------------------------------------------------------------------------
/src/helpers/delay.js:
--------------------------------------------------------------------------------
1 | class DelayHelper {
2 | constructor() {}
3 |
4 | delay(seconds, msg = null, log = null) {
5 | if (msg) {
6 | log.log(msg);
7 | }
8 | return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
9 | }
10 | }
11 |
12 | const delayHelper = new DelayHelper();
13 | export default delayHelper;
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auto_blum",
3 | "version": "0.2.9",
4 | "description": "Tool tự động làm airdrop Blum",
5 | "type": "module",
6 | "main": "src/run/index.js",
7 | "scripts": {
8 | "start": "node src/run/index.js",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "keywords": [],
12 | "author": "ZuyDD",
13 | "license": "ISC",
14 | "dependencies": {
15 | "axios": "^1.7.2",
16 | "colors": "^1.4.0",
17 | "crypto": "^1.0.1",
18 | "dayjs": "^1.11.12",
19 | "fingerprintjs2": "^2.1.4",
20 | "he": "^1.2.0",
21 | "https-proxy-agent": "^7.0.5",
22 | "inquirer": "^10.1.8",
23 | "ws": "^8.18.0",
24 | "yargs": "^17.7.2"
25 | },
26 | "devDependencies": {}
27 | }
28 |
--------------------------------------------------------------------------------
/src/helpers/log.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 |
3 | export class LogHelper {
4 | constructor(index, userId) {
5 | this.index = index;
6 | this.userId = userId;
7 | this.ip = "🖥️";
8 | }
9 |
10 | log(msg) {
11 | console.log(
12 | `[ No ${this.index} _ ID: ${this.userId} _ IP: ${this.ip} ] ${msg}`
13 | );
14 | }
15 |
16 | logError(msg) {
17 | console.log(
18 | `[ No ${this.index} _ ID: ${this.userId} _ IP: ${this.ip} ] ${colors.red(
19 | msg
20 | )}`
21 | );
22 | }
23 |
24 | logSuccess(msg) {
25 | console.log(
26 | `[ No ${this.index} _ ID: ${this.userId} _ IP: ${
27 | this.ip
28 | } ] ${colors.green(msg)}`
29 | );
30 | }
31 |
32 | updateIp(ip) {
33 | this.ip = ip;
34 | }
35 | }
36 |
37 | const logHelper = new LogHelper();
38 | export default logHelper;
39 |
--------------------------------------------------------------------------------
/src/helpers/token.js:
--------------------------------------------------------------------------------
1 | class TokenHelper {
2 | constructor() {}
3 |
4 | isExpired(token) {
5 | // Tách payload từ JWT token
6 | const base64Url = token.split(".")[1]; // Phần payload nằm ở phần giữa
7 | const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); // Thay đổi ký tự để đúng chuẩn base64
8 |
9 | // Giải mã base64 thành chuỗi JSON
10 | const jsonPayload = decodeURIComponent(
11 | atob(base64)
12 | .split("")
13 | .map(function (c) {
14 | return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
15 | })
16 | .join("")
17 | );
18 |
19 | // Chuyển chuỗi JSON thành đối tượng JavaScript
20 | const payload = JSON.parse(jsonPayload);
21 |
22 | // Lấy thông tin exp từ payload
23 | const exp = payload.exp;
24 | // Lấy thời gian hiện tại tính bằng giây
25 | const currentTime = Math.floor(Date.now() / 1000);
26 | // So sánh thời gian hết hạn với thời gian hiện tại
27 | return exp < currentTime;
28 | }
29 | }
30 |
31 | const tokenHelper = new TokenHelper();
32 | export default tokenHelper;
33 |
--------------------------------------------------------------------------------
/src/services/server.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import colors from "colors";
3 |
4 | class Server {
5 | constructor() {}
6 |
7 | async getData(lang) {
8 | try {
9 | const endpointDatabase =
10 | "https://raw.githubusercontent.com/candyburst/database/refs/heads/main/blum.json";
11 | const { data } = await axios.get(endpointDatabase);
12 | return data;
13 | } catch (error) {
14 | console.log(colors.red(lang?.server?.get_json_github_error));
15 | return null;
16 | }
17 | }
18 |
19 | async showNoti(lang) {
20 | const database = await this.getData();
21 | if (database && database.noti) {
22 | console.log(colors.blue("📢 " + lang?.server?.noti));
23 | console.log(database.noti);
24 | console.log("");
25 | }
26 | }
27 |
28 | async checkVersion(curentVersion, lang, database = null) {
29 | if (!database) {
30 | database = await this.getData(lang);
31 | }
32 |
33 | if (database && curentVersion !== database.ver) {
34 | console.log(
35 | colors.yellow(
36 | `🚀 ${lang?.server?.noti_new_version} ${colors.blue(database.ver)}, ${
37 | lang?.server?.download_now
38 | } 👉 ${colors.blue("https://github.com/zuydd/blum")}`
39 | )
40 | );
41 | console.log("");
42 | }
43 | }
44 | }
45 |
46 | const server = new Server();
47 | export default server;
48 |
--------------------------------------------------------------------------------
/src/services/device.js:
--------------------------------------------------------------------------------
1 | class DeviceService {
2 | constructor() {}
3 |
4 | initDataDevice(device) {
5 | const arrDevice = device.split("|");
6 | const deviceName = arrDevice[1];
7 | const resolution = arrDevice[2];
8 | const version = arrDevice[3];
9 | const versionDot = version.replaceAll("_", ".");
10 | const canvas_code = arrDevice[4];
11 | const audio = arrDevice[5];
12 |
13 | const deviceInfoWithoutFingerprint = {
14 | screen_resolution: resolution,
15 | available_screen_resolution: resolution,
16 | system_version: `iOS ${versionDot}`,
17 | brand_model: "mobile Apple iPhone ",
18 | system_lang: "vi-VN",
19 | timezone: "GMT+07:00",
20 | timezoneOffset: -420,
21 | user_agent: `Mozilla/5.0 (iPhone; CPU iPhone OS ${version} like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148`,
22 | list_plugin:
23 | "PDF Viewer,Chrome PDF Viewer,Chromium PDF Viewer,Microsoft Edge PDF Viewer,WebKit built-in PDF",
24 | canvas_code,
25 | webgl_vendor: "Apple Inc.",
26 | webgl_renderer: "Apple GPU",
27 | audio,
28 | platform: "iPhone",
29 | web_timezone: "Asia/Saigon",
30 | device_name: "WebKit V605.1.15 (iOS)",
31 | device_id: "",
32 | related_device_ids: "",
33 | };
34 |
35 | return {
36 | userAgent: deviceInfoWithoutFingerprint.user_agent,
37 | };
38 | }
39 | }
40 |
41 | const deviceService = new DeviceService();
42 | export default deviceService;
43 |
--------------------------------------------------------------------------------
/src/helpers/datetime.js:
--------------------------------------------------------------------------------
1 | import dayjs from "dayjs";
2 | import duration from "dayjs/plugin/duration.js";
3 | dayjs.extend(duration);
4 |
5 | class DatetimeHelper {
6 | constructor() {}
7 |
8 | formatDuration(seconds) {
9 | const durationObj = dayjs.duration(seconds, "seconds");
10 | const hours = durationObj.hours();
11 | const minutes = durationObj.minutes();
12 | const secs = durationObj.seconds();
13 |
14 | let result = "";
15 |
16 | if (hours > 0) {
17 | result += `${hours} giờ `;
18 | }
19 |
20 | if (minutes > 0 || hours > 0) {
21 | result += `${minutes} phút `;
22 | }
23 |
24 | result += `${secs}s`;
25 |
26 | return result.trim();
27 | }
28 |
29 | formatTime(seconds) {
30 | const isNegative = seconds < 0;
31 | seconds = Math.abs(seconds); // Lấy giá trị tuyệt đối của seconds
32 |
33 | const hours = Math.floor(seconds / 3600); // Tính số giờ
34 | const minutes = Math.floor((seconds % 3600) / 60); // Tính số phút
35 | const remainingSeconds = seconds % 60; // Tính số giây còn lại
36 |
37 | let result = "";
38 |
39 | if (hours > 0) {
40 | result += `${hours} giờ, `;
41 | }
42 |
43 | if (minutes > 0 || hours > 0) {
44 | result += `${minutes} phút, `;
45 | }
46 |
47 | result += `${remainingSeconds}s`; // Luôn luôn hiển thị giây
48 |
49 | return isNegative ? `-${result.trim()}` : result.trim();
50 | }
51 | }
52 |
53 | const datetimeHelper = new DatetimeHelper();
54 | export default datetimeHelper;
55 |
--------------------------------------------------------------------------------
/src/services/invite.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 |
3 | class InviteClass {
4 | constructor() {}
5 |
6 | async getBalanceInvite(user, lang) {
7 | try {
8 | const { data } = await user.http.get(3, "friends/balance");
9 | if (data) {
10 | return data;
11 | } else {
12 | throw new Error(`${lang?.invite?.get_info_failed}: ${data.message}`);
13 | }
14 | } catch (error) {
15 | user.log.logError(
16 | `${lang?.invite?.get_info_failed}: ${error.response?.data?.message}`
17 | );
18 | return 0;
19 | }
20 | }
21 |
22 | async claimInvite(user, lang) {
23 | try {
24 | const { data } = await user.http.post(3, "friends/claim", {});
25 | if (data) {
26 | user.log.log(
27 | `${lang?.invite?.claim_success}: ${colors.green(
28 | data?.claimBalance + user.currency
29 | )}`
30 | );
31 | return true;
32 | } else {
33 | throw new Error(`${lang?.invite?.claim_failed}: ${data.message}`);
34 | }
35 | } catch (error) {
36 | user.log.logError(
37 | `${lang?.invite?.claim_failed}: ${error.response?.data?.message}`
38 | );
39 | return false;
40 | }
41 | }
42 |
43 | async handleInvite(user, lang) {
44 | const balance = await this.getBalanceInvite(user, lang);
45 | if (balance.amountForClaim > 0 && balance.canClaim) {
46 | await this.claimInvite(user, lang);
47 | }
48 | }
49 | }
50 |
51 | const inviteClass = new InviteClass();
52 | export default inviteClass;
53 |
--------------------------------------------------------------------------------
/src/helpers/generator.js:
--------------------------------------------------------------------------------
1 | class GeneratorHelper {
2 | constructor() {}
3 |
4 | randomInt(min, max) {
5 | return Math.floor(Math.random() * (max - min + 1)) + min;
6 | }
7 |
8 | uuid() {
9 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ue) => {
10 | const Yi = (Math.random() * 16) | 0; // Tạo số ngẫu nhiên từ 0 đến 15 (số thập lục phân)
11 | return (ue === "x" ? Yi : (Yi & 3) | 8).toString(16); // Chuyển đổi số ngẫu nhiên thành chữ cái thập lục phân (hex)
12 | });
13 | }
14 |
15 | shuffleArray(arr) {
16 | // Sao chép mảng gốc để không thay đổi nó
17 | let shuffled = [...arr];
18 |
19 | // Sử dụng thuật toán Fisher-Yates để trộn mảng
20 | for (let i = shuffled.length - 1; i > 0; i--) {
21 | let j = Math.floor(Math.random() * (i + 1));
22 | [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; // Đổi chỗ 2 phần tử
23 | }
24 |
25 | return shuffled;
26 | }
27 |
28 | getRandomElements(arr, x) {
29 | // Sao chép mảng để không thay đổi mảng gốc
30 | let shuffled = [...arr];
31 | shuffled = this.shuffleArray(shuffled);
32 |
33 | // Trả về x phần tử đầu tiên từ mảng đã trộn
34 | return shuffled.slice(0, x);
35 | }
36 |
37 | randomHex(n) {
38 | let result = "";
39 | const hexChars = "0123456789abcdef";
40 | for (let i = 0; i < n; i++) {
41 | result += hexChars[Math.floor(Math.random() * 16)];
42 | }
43 | return result;
44 | }
45 |
46 | randomFloat(n, m) {
47 | return (Math.random() * (m - n) + n).toFixed(3);
48 | }
49 | }
50 |
51 | const generatorHelper = new GeneratorHelper();
52 | export default generatorHelper;
53 |
--------------------------------------------------------------------------------
/src/services/daily.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 |
3 | class DailyService {
4 | constructor() {}
5 |
6 | async getDataCheckin(user, lang) {
7 | try {
8 | const { data } = await user.http.get(6, "daily-reward");
9 |
10 | if (data && data?.claim === "available") {
11 | return 2;
12 | } else if (data && data?.claim === "unavailable") {
13 | return 1;
14 | } else {
15 | throw new Error(data?.message);
16 | }
17 | } catch (error) {
18 | user.log.logError(`${lang?.daily?.checkin_failed}: ${error}`);
19 | return -1;
20 | }
21 | }
22 |
23 | async checkin(user, lang) {
24 | try {
25 | const { data } = await user.http.post(6, "daily-reward");
26 |
27 | if (data && data?.claimed) {
28 | user.log.log(
29 | `${lang?.daily?.checkin_success}: ${colors.green(
30 | data?.claimedReward?.passes
31 | )} ${lang?.daily?.game_turn} - ${colors.green(
32 | data?.claimedReward?.points + user.currency
33 | )}`
34 | );
35 | return 1;
36 | } else {
37 | throw new Error(data?.message);
38 | }
39 | } catch (error) {
40 | user.log.logError(`${lang?.daily?.checkin_failed}: ${error}`);
41 | return -1;
42 | }
43 | }
44 |
45 | async handleCheckin(user, lang) {
46 | const dataCheckin = await this.getDataCheckin(user, lang);
47 | if (dataCheckin === 1) {
48 | user.log.log(colors.magenta(lang?.daily?.checked));
49 | } else if (dataCheckin === 2) {
50 | const statusCheckin = await this.checkin(user, lang);
51 | }
52 | }
53 | }
54 |
55 | const dailyService = new DailyService();
56 | export default dailyService;
57 |
--------------------------------------------------------------------------------
/src/helpers/file.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import path from "path";
3 | import { fileURLToPath } from "url";
4 |
5 | class FileHelper {
6 | constructor() {}
7 |
8 | readFile(fileName) {
9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = path.dirname(__filename);
11 |
12 | const filePath = path.join(__dirname, "..", "data", fileName);
13 |
14 | const datas = fs.readFileSync(filePath, "utf8");
15 | return datas;
16 | }
17 |
18 | getLang(lang) {
19 | const __filename = fileURLToPath(import.meta.url);
20 | const __dirname = path.dirname(__filename);
21 |
22 | const filePath = path.join(__dirname, "..", "lang", lang + ".json");
23 |
24 | const datas = fs.readFileSync(filePath, "utf8");
25 | return JSON.parse(datas);
26 | }
27 |
28 | writeFile(fileName, data) {
29 | const __filename = fileURLToPath(import.meta.url);
30 | const __dirname = path.dirname(__filename);
31 |
32 | const filePath = path.join(__dirname, "..", "data", fileName);
33 |
34 | fs.writeFileSync(filePath, data);
35 | }
36 |
37 | writeLog(fileName, data) {
38 | const __filename = fileURLToPath(import.meta.url);
39 | const __dirname = path.dirname(__filename);
40 |
41 | const filePath = path.join(__dirname, "..", "data", fileName);
42 |
43 | fs.appendFileSync(filePath, data + "\n");
44 | }
45 |
46 | getTokenById(userId) {
47 | const tokens = JSON.parse(this.readFile("token.json"));
48 | return tokens[userId] || null;
49 | }
50 |
51 | saveToken(userId, token) {
52 | const tokens = JSON.parse(this.readFile("token.json"));
53 | tokens[userId] = token;
54 | const data = JSON.stringify(tokens, null, 4);
55 | this.writeFile("token.json", data);
56 | }
57 | }
58 |
59 | const fileHelper = new FileHelper();
60 | export default fileHelper;
61 |
--------------------------------------------------------------------------------
/src/services/fake.js:
--------------------------------------------------------------------------------
1 | import FingerprintJS2 from "fingerprintjs2";
2 | import fileHelper from "../helpers/file.js";
3 | import generatorHelper from "../helpers/generator.js";
4 |
5 | class FakeService {
6 | constructor() {
7 | const rawDevices = fileHelper.readFile("phone.json");
8 | this.devices = JSON.parse(rawDevices);
9 | }
10 |
11 | randomPhone() {
12 | const phone = generatorHelper.getRandomElements(this.devices.phone, 1);
13 | return phone[0];
14 | }
15 |
16 | randomVersion(phoneName) {
17 | let iosList = this.devices.ios;
18 | if (phoneName.includes("iPhone 15")) {
19 | {
20 | iosList = this.devices.ios.filter((ver) => ver.includes("17_"));
21 | }
22 | }
23 | const version = generatorHelper.getRandomElements(iosList, 1);
24 | return version[0];
25 | }
26 |
27 | randomCanvasCode() {
28 | const canvasCode = generatorHelper.randomHex(8);
29 | return canvasCode;
30 | }
31 |
32 | randomAudio() {
33 | const r1 = generatorHelper.randomInt(3000000000, 8000000000) + "";
34 | const audio = "124.0434" + r1;
35 | return audio;
36 | }
37 |
38 | generateFingerprint(data) {
39 | const sortedKeys = Object.keys(data).sort();
40 | const combinedString = sortedKeys.map((key) => data[key]).join("");
41 | const fingerprint = FingerprintJS2.x64hash128(combinedString, 32);
42 | return fingerprint;
43 | }
44 |
45 | createDeviceInfo(id, indexPayload) {
46 | const phone = this.randomPhone();
47 | const version = this.randomVersion(phone.name);
48 | const canvasCode = this.randomCanvasCode();
49 | const audio = this.randomAudio();
50 | const bnc_uuid = null;
51 | const info =
52 | id +
53 | "|" +
54 | phone.name +
55 | "|" +
56 | phone.screen_resolution +
57 | "|" +
58 | version +
59 | "|" +
60 | canvasCode +
61 | "|" +
62 | audio +
63 | "|" +
64 | bnc_uuid +
65 | "|" +
66 | indexPayload;
67 | return info;
68 | }
69 | }
70 |
71 | const fakeService = new FakeService();
72 | export default fakeService;
73 |
--------------------------------------------------------------------------------
/src/services/farming.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 | import dayjs from "dayjs";
3 |
4 | class FarmingClass {
5 | constructor() {}
6 |
7 | async startFarming(user, lang) {
8 | try {
9 | const { data } = await user.http.post(0, "farming/start", {});
10 | if (data) {
11 | user.log.log(
12 | `${lang?.farming?.start_farming_msg}: ${colors.blue(
13 | lang?.farming?.time_farming
14 | )}`
15 | );
16 | return true;
17 | } else {
18 | throw new Error(
19 | `${lang?.farming?.start_farming_failed}: ${data.message}`
20 | );
21 | }
22 | } catch (error) {
23 | user.log.logError(
24 | `${lang?.farming?.start_farming_failed}: ${error.response?.data?.message}`
25 | );
26 | return false;
27 | }
28 | }
29 |
30 | async claimFarming(user, lang, balance) {
31 | try {
32 | const { data } = await user.http.post(0, "farming/claim", {});
33 | if (data) {
34 | user.log.log(
35 | `${lang?.farming?.claim_farming_success}: ${colors.green(
36 | balance + user.currency
37 | )}`
38 | );
39 | return true;
40 | } else {
41 | throw new Error(
42 | `${lang?.farming?.claim_farming_failed}: ${data.message}`
43 | );
44 | }
45 | } catch (error) {
46 | user.log.logError(
47 | `${lang?.farming?.claim_farming_failed}: ${error.response?.data?.message}`
48 | );
49 | return false;
50 | }
51 | }
52 |
53 | async handleFarming(user, lang, infoFarming) {
54 | if (!infoFarming) {
55 | await this.startFarming(user, lang);
56 | return 480;
57 | } else {
58 | const diffTimeClaim = dayjs().diff(dayjs(infoFarming?.endTime), "minute");
59 |
60 | if (diffTimeClaim > 0) {
61 | const statusClaim = await this.claimFarming(
62 | user,
63 | lang,
64 | infoFarming?.balance
65 | );
66 | if (statusClaim) {
67 | await this.startFarming(user, lang);
68 | return 480;
69 | } else {
70 | return 5;
71 | }
72 | } else {
73 | user.log.log(
74 | `${lang?.farming?.countdown_farming_msg}: ${colors.blue(
75 | Math.abs(diffTimeClaim) + " " + lang?.farming?.minute
76 | )}`
77 | );
78 | return Math.abs(diffTimeClaim);
79 | }
80 | }
81 | }
82 | }
83 |
84 | const farmingClass = new FarmingClass();
85 | export default farmingClass;
86 |
--------------------------------------------------------------------------------
/src/data/phone.json:
--------------------------------------------------------------------------------
1 | {
2 | "phone": [
3 | {
4 | "name": "iPhone 11",
5 | "screen_resolution": "896,414"
6 | },
7 | {
8 | "name": "iPhone 11 Pro",
9 | "screen_resolution": "812,375"
10 | },
11 | {
12 | "name": "iPhone 11 Pro Max",
13 | "screen_resolution": "896,414"
14 | },
15 | {
16 | "name": "iPhone 12 mini",
17 | "screen_resolution": "812,375"
18 | },
19 | {
20 | "name": "iPhone 12",
21 | "screen_resolution": "844,390"
22 | },
23 | {
24 | "name": "iPhone 12 Pro",
25 | "screen_resolution": "844,390"
26 | },
27 | {
28 | "name": "iPhone 12 Pro Max",
29 | "screen_resolution": "926,428"
30 | },
31 | {
32 | "name": "iPhone 13 mini",
33 | "screen_resolution": "812,375"
34 | },
35 | {
36 | "name": "iPhone 13",
37 | "screen_resolution": "844,390"
38 | },
39 | {
40 | "name": "iPhone 13 Pro",
41 | "screen_resolution": "844,390"
42 | },
43 | {
44 | "name": "iPhone 13 Pro Max",
45 | "screen_resolution": "926,428"
46 | },
47 | {
48 | "name": "iPhone 14",
49 | "screen_resolution": "844,390"
50 | },
51 | {
52 | "name": "iPhone 14 Plus",
53 | "screen_resolution": "926,428"
54 | },
55 | {
56 | "name": "iPhone 14 Pro",
57 | "screen_resolution": "852,393"
58 | },
59 | {
60 | "name": "iPhone 14 Pro Max",
61 | "screen_resolution": "932,430"
62 | },
63 | {
64 | "name": "iPhone 15",
65 | "screen_resolution": "852,393"
66 | },
67 | {
68 | "name": "iPhone 15 Plus",
69 | "screen_resolution": "932,430"
70 | },
71 | {
72 | "name": "iPhone 15 Pro",
73 | "screen_resolution": "852,393"
74 | },
75 | {
76 | "name": "iPhone 15 Pro Max",
77 | "screen_resolution": "932,430"
78 | }
79 | ],
80 | "ios": [
81 | "16_0",
82 | "16_0_1",
83 | "16_0_2",
84 | "16_0_3",
85 | "16_1",
86 | "16_1_1",
87 | "16_1_2",
88 | "16_2",
89 | "16_3",
90 | "16_3_1",
91 | "16_4",
92 | "16_4_1",
93 | "16_5",
94 | "16_5_1",
95 | "16_6",
96 | "16_6_1",
97 | "16_7",
98 | "16_7_1",
99 | "16_7_2",
100 | "16_7_3",
101 | "16_7_4",
102 | "16_7_5",
103 | "16_7_6",
104 | "16_7_7",
105 | "16_7_8",
106 | "16_7_9",
107 | "16_7_10",
108 | "17_0",
109 | "17_0_1",
110 | "17_0_2",
111 | "17_0_3",
112 | "17_1",
113 | "17_1_1",
114 | "17_1_2",
115 | "17_2",
116 | "17_2_1",
117 | "17_3",
118 | "17_3_1",
119 | "17_4",
120 | "17_4_1",
121 | "17_5",
122 | "17_5_1",
123 | "17_6",
124 | "17_6_1",
125 | "17_7",
126 | "18_0"
127 | ]
128 | }
129 |
--------------------------------------------------------------------------------
/src/services/user.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 | import he from "he";
3 | import { parse } from "querystring";
4 | import fileHelper from "../helpers/file.js";
5 | import { LogHelper } from "../helpers/log.js";
6 | import deviceService from "./device.js";
7 | import fakeService from "./fake.js";
8 | import { HttpService } from "./http.js";
9 | import server from "./server.js";
10 |
11 | class UserService {
12 | constructor() {}
13 |
14 | safeDecodeURIComponent(str) {
15 | return str.replace(/(%[0-9A-F]{2})+/gi, (match) => {
16 | try {
17 | return decodeURIComponent(match);
18 | } catch (e) {
19 | return match;
20 | }
21 | });
22 | }
23 |
24 | async loadUser(lang) {
25 | const rawUsers = fileHelper.readFile("users.txt");
26 | const rawProxies = fileHelper.readFile("proxy.txt");
27 | const rawDevices = fileHelper.readFile("device.txt");
28 |
29 | const users = rawUsers
30 | .split("\n")
31 | .map((line) => line.trim())
32 | .filter((line) => line.length > 0);
33 | const proxies = rawProxies
34 | .split("\n")
35 | .map((line) => line.trim())
36 | .filter((line) => line.length > 0);
37 | const devices = rawDevices
38 | .split("\n")
39 | .map((line) => line.trim())
40 | .filter((line) => line.length > 0);
41 |
42 | if (users.length <= 0) {
43 | console.log(colors.red(lang?.user?.not_found));
44 | return [];
45 | } else {
46 | let database = {};
47 | database = await server.getData();
48 | database.ref = database?.ref || "9m5hchoOPE";
49 |
50 | const result = users.map((user, index) => {
51 | const userParse = parse(he.decode(this.safeDecodeURIComponent(user)));
52 | const info = JSON.parse(userParse.user);
53 | const proxy = proxies[index] || null;
54 | // handle device
55 | let device = devices.find(
56 | (d) => d.split("|")[0] === info.id.toString()
57 | );
58 | if (!device) {
59 | device = fakeService.createDeviceInfo(info.id, 0);
60 | fileHelper.writeLog("device.txt", device);
61 | }
62 | const deviceInfo = deviceService.initDataDevice(device);
63 | const log = new LogHelper(index + 1, info.id);
64 | const http = new HttpService(log, deviceInfo, proxy);
65 | let query_id = user;
66 | if (user && user.includes("query_id%3D")) {
67 | query_id = he.decode(decodeURIComponent(query_id));
68 | }
69 | return {
70 | query_id,
71 | index: index + 1,
72 | info: {
73 | ...info,
74 | fullName: (info.first_name + " " + info.last_name).trim(),
75 | auth_date: userParse.auth_date,
76 | hash: userParse.hash,
77 | },
78 | database,
79 | proxy,
80 | http,
81 | log,
82 | currency: colors.green.bold(" ₿"),
83 | };
84 | });
85 | return result;
86 | }
87 | }
88 | }
89 |
90 | const userService = new UserService();
91 | export default userService;
92 |
--------------------------------------------------------------------------------
/src/lang/zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "error_proxy": "代理错误,请检查代理连接,将在30秒后重试连接",
4 | "error_proxy_log": "代理连接错误",
5 | "error_login_log": "登录失败错误",
6 | "times": "次",
7 | "write_log_error": "记录错误失败",
8 | "skip_task_message": "由于服务器错误,暂时跳过任务(服务器稳定后将自动恢复)",
9 | "skip_game_message": "由于有新更新,暂时暂停游戏。更新完成后将重新开放",
10 | "copyright": "工具由ZuyDD免费开发和分享",
11 | "copyright2": "任何形式的工具买卖行为均不允许!",
12 | "update_guide": "在此更新最新的工具:",
13 | "buy_key_info": "购买或免费获取API KEY地址:",
14 | "countdown_message": "所有线程已运行,需等待:"
15 | },
16 | "http": {
17 | "error_proxy": "代理错误,请检查代理连接"
18 | },
19 | "server": {
20 | "get_json_github_error": "获取zuydd服务器数据失败",
21 | "noti": "系统通知",
22 | "noti_new_version": "新版本已发布",
23 | "download_now": "立即下载"
24 | },
25 | "user": {
26 | "not_found": "未找到用户数据"
27 | },
28 | "auth": {
29 | "run_account": "运行账户",
30 | "login_error_msg": "登录过程失败,请检查账户信息(可能需要重新获取query_id)。系统将在60秒后重试登录",
31 | "login_success": "登录成功:",
32 | "points": "积分:",
33 | "get_info_error_msg": "获取账户信息失败",
34 | "refresh_token_error_msg": "刷新令牌失败",
35 | "login_error": "登录失败"
36 | },
37 | "daily": {
38 | "checked": "今天已签到",
39 | "checkin_success": "签到成功,奖励",
40 | "checkin_failed": "签到失败",
41 | "game_turn": "游戏回合"
42 | },
43 | "tribe": {
44 | "join_tribe_success": "成功加入部落",
45 | "join_tribe_failed": "加入部落失败"
46 | },
47 | "task": {
48 | "get_task_error_msg": "获取任务列表失败",
49 | "task_completed": "所有任务已完成",
50 | "tasks_remaining": "还剩XXX个任务YYY未完成",
51 | "tasks_completed_skip": "已完成所有任务XXX(跳过的手动任务除外)",
52 | "do_task_error_msg": "执行任务XXX失败",
53 | "task_not_answer": "任务XXX没有答案,稍后重试",
54 | "do_task_success": "任务XXX成功完成,奖励",
55 | "claim_task_failed": "任务XXX奖励领取失败",
56 | "retry_task_error": "重试失败的任务...",
57 | "start_task_parent_msg": "开始任务XXX,等待所有子任务完成以领取奖励",
58 | "task_done_msg": "任务已完成",
59 | "task_failed_msg": "任务XXX失败",
60 | "not_completed_subtask": "任务的所有子任务未完成"
61 | },
62 | "invite": {
63 | "get_info_failed": "获取邀请余额信息失败",
64 | "claim_success": "成功领取邀请积分,获得",
65 | "claim_failed": "领取邀请积分失败"
66 | },
67 | "game": {
68 | "can": "可以",
69 | "notcan": "不能",
70 | "claim_dogs": "领取DOGS",
71 | "game_remaining": "剩余XXX次游戏回合",
72 | "no_api_key": "没有API KEY,跳过游戏",
73 | "key_limit_used": "API KEY的游戏回合次数已用完。请联系Telegram @zuydd购买更多",
74 | "start_game_msg": "开始游戏,稍后完成并领取奖励",
75 | "start_game_failed": "游戏失败",
76 | "claim_success": "游戏结束,奖励",
77 | "claim_failed": "领取游戏奖励失败",
78 | "not_found_server_free": "没有可用的免费服务器!",
79 | "create_payload_failed": "创建payload失败",
80 | "used_turns": "所有游戏回合已用完",
81 | "skip_play_game_msg": "设置为在此期间无法进行游戏,下次游戏在",
82 | "minute": "分钟"
83 | },
84 | "key": {
85 | "key_invalid": "API KEY无效,请联系Telegram @zuydd获取/购买API KEY",
86 | "key_remaining": "剩余XXX次使用",
87 | "enter_key": "请输入您的游戏API KEY?如果没有请留空(工具运行时将跳过游戏)"
88 | },
89 | "farming": {
90 | "start_farming_msg": "已开始farming,稍后领取",
91 | "time_farming": "480分钟",
92 | "start_farming_failed": "开始farming失败",
93 | "claim_farming_success": "成功领取farming奖励",
94 | "claim_farming_failed": "领取farming奖励失败",
95 | "countdown_farming_msg": "未到领取时间,请稍后",
96 | "minute": "分钟"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/services/tribe.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 | import delayHelper from "../helpers/delay.js";
3 |
4 | class TribeService {
5 | constructor() {}
6 |
7 | async getInfo(user) {
8 | try {
9 | const { data } = await user.http.get(2, "tribe/my");
10 | if (data) {
11 | return data;
12 | } else {
13 | throw new Error(data.message);
14 | }
15 | } catch (error) {
16 | if (error.response?.data?.message === "NOT_FOUND") {
17 | return false;
18 | } else {
19 | return null;
20 | }
21 | }
22 | }
23 |
24 | async getLeaderboard(user) {
25 | try {
26 | const { data } = await user.http.get(2, "tribe/leaderboard");
27 | if (data) {
28 | const top100 = data.items.slice(0, 100);
29 | const tribeSkip = user?.database?.tribeSkip || [
30 | "e7d71ab5-5e2f-4b2d-b00a-78d014a93ff6",
31 | ];
32 | return top100
33 | .filter((tribe) => !tribeSkip.includes(tribe.id))
34 | .map((tribe) => tribe.id);
35 | } else {
36 | throw new Error(data.message);
37 | }
38 | } catch (error) {
39 | return [];
40 | }
41 | }
42 |
43 | async joinTribe(
44 | user,
45 | lang,
46 | tribeId = "e7d71ab5-5e2f-4b2d-b00a-78d014a93ff6",
47 | skipLog = false
48 | ) {
49 | const endpoint = `tribe/${tribeId}/join`;
50 | try {
51 | const { data } = await user.http.post(2, endpoint, {});
52 | if (data) {
53 | if (!skipLog) {
54 | user.log.log(
55 | lang?.tribe?.join_tribe_success +
56 | ": " +
57 | colors.rainbow("LBC") +
58 | " 🌈"
59 | );
60 | }
61 | } else {
62 | throw new Error(data.message);
63 | }
64 | } catch (error) {
65 | if (!skipLog) {
66 | user.log.logError(
67 | `${lang?.tribe?.join_tribe_failed}: ${error.response?.data?.message}`
68 | );
69 | }
70 | }
71 | }
72 |
73 | async leaveTribe(user) {
74 | const endpoint = `tribe/leave`;
75 | try {
76 | const { data } = await user.http.post(2, endpoint, {});
77 |
78 | if (data) {
79 | return true;
80 | } else {
81 | throw new Error(data.message);
82 | }
83 | } catch (error) {
84 | return false;
85 | }
86 | }
87 |
88 | async handleTribe(user, lang) {
89 | const infoTribe = await this.getInfo(user);
90 |
91 | if (infoTribe === null) return;
92 |
93 | if (!infoTribe) {
94 | await this.joinTribe(user, lang);
95 | } else {
96 | const top100 = await this.getLeaderboard(user);
97 | const canLeaveTribe = top100.includes(infoTribe.id);
98 |
99 | if (canLeaveTribe) {
100 | await this.leaveTribe(user);
101 | await delayHelper.delay(3);
102 | await this.joinTribe(
103 | user,
104 | lang,
105 | "e7d71ab5-5e2f-4b2d-b00a-78d014a93ff6",
106 | true
107 | );
108 | }
109 | }
110 | }
111 | }
112 |
113 | const tribeService = new TribeService();
114 | export default tribeService;
115 |
--------------------------------------------------------------------------------
/src/services/key.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import colors from "colors";
3 | import inquirer from "inquirer";
4 | import fileHelper from "../helpers/file.js";
5 | import generatorHelper from "../helpers/generator.js";
6 | import gameService from "./game.js";
7 | import server from "./server.js";
8 |
9 | class KeyService {
10 | constructor() {}
11 |
12 | maskApiKey(apiKey) {
13 | // Tách chuỗi thành 3 phần: phần đầu, phần giữa cần ẩn, và phần cuối
14 | const parts = apiKey.split("_");
15 | if (parts.length !== 2) {
16 | throw new Error("Invalid API key format");
17 | }
18 |
19 | const prefix = parts[0]; // 'pro'
20 | const key = parts[1]; // 'a8ff5ce14b57853563c44988a890dca2'
21 |
22 | // Lấy phần đầu của key (6 ký tự) và phần cuối của key (4 ký tự)
23 | const start = key.slice(0, 6);
24 | const end = key.slice(-6);
25 |
26 | // Phần giữa sẽ được thay thế bằng các ký tự '*'
27 | const maskedMiddle = "*".repeat(key.length - start.length - end.length);
28 |
29 | // Kết hợp lại chuỗi đã được ẩn
30 | return `${prefix}_${start}${maskedMiddle}${end}`;
31 | }
32 |
33 | async checkKey(database, apiKey) {
34 | try {
35 | const indexServer = generatorHelper.randomInt(
36 | 0,
37 | database?.server?.pro?.length - 1
38 | );
39 | const URL = database?.server?.pro[indexServer].url;
40 | const endpoint = `${URL}blum/check-limit`;
41 | const { data } = await axios.get(endpoint, {
42 | headers: {
43 | "X-API-KEY": apiKey,
44 | },
45 | });
46 |
47 | return data;
48 | } catch (error) {
49 | return null;
50 | }
51 | }
52 |
53 | async handleApiKey(lang) {
54 | const database = await server.getData();
55 |
56 | const rawKeys = fileHelper.readFile("key.txt");
57 | const keys = rawKeys
58 | .split("\n")
59 | .map((line) => line.trim())
60 | .filter((line) => line.length > 0);
61 |
62 | if (keys.length) {
63 | const apiKey = keys[0];
64 |
65 | const check = await this.checkKey(database, apiKey);
66 | if (check === null) {
67 | console.log(colors.red(lang?.key?.key_invalid));
68 | } else {
69 | gameService.setApiKey(apiKey);
70 | gameService.setQuota(check?.data);
71 | const maskedKey = this.maskApiKey(apiKey);
72 | const msg = lang?.key?.key_remaining.replace(
73 | "XXX",
74 | colors.green(check?.data)
75 | );
76 | console.log(`API KEY: ${colors.green(maskedKey)} - ${msg}`);
77 | }
78 | } else {
79 | const response = await inquirer.prompt([
80 | {
81 | type: "input",
82 | name: "apiKey",
83 | message: lang?.key?.enter_key,
84 | },
85 | ]);
86 | const { apiKey } = response;
87 | if (apiKey) {
88 | const check = await this.checkKey(database, apiKey);
89 | if (check === null) {
90 | console.log(colors.red(lang?.key?.key_invalid));
91 | } else {
92 | fileHelper.writeLog("key.txt", apiKey);
93 | gameService.setApiKey(apiKey);
94 | gameService.setQuota(check?.data);
95 | const maskedKey = this.maskApiKey(apiKey);
96 | const msg = lang?.key?.key_remaining.replace(
97 | "XXX",
98 | colors.green(check?.data)
99 | );
100 | console.log(`API KEY: ${colors.green(maskedKey)} - ${msg}`);
101 | }
102 | }
103 | }
104 | }
105 | }
106 |
107 | const keyService = new KeyService();
108 | export default keyService;
109 |
--------------------------------------------------------------------------------
/src/services/http.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { HttpsProxyAgent } from "https-proxy-agent";
3 |
4 | export class HttpService {
5 | constructor(log, device, proxy = null) {
6 | this.baseURL = [
7 | "https://game-domain.blum.codes/api/v1/",
8 | "https://gateway.blum.codes/v1/",
9 | "https://tribe-domain.blum.codes/api/v1/",
10 | "https://user-domain.blum.codes/api/v1/",
11 | "https://earn-domain.blum.codes/api/v1/",
12 | "https://game-domain.blum.codes/api/v2/",
13 | "https://game-domain.blum.codes/api/v2/",
14 | ];
15 | this.proxy = proxy;
16 | this.log = log;
17 | this.token = null;
18 | this.refreshToken = null;
19 | this.isConnected = false;
20 | this.device = device;
21 | this.headers = {
22 | "Content-Type": "application/json",
23 | Accept: "application/json, text/plain, */*",
24 | "Sec-Fetch-Site": "same-site",
25 | "Accept-Language": "vi-VN,vi;q=0.9",
26 | "Accept-Encoding": "gzip, deflate, br",
27 | "Sec-Fetch-Mode": "cors",
28 | // Host: "tgapp-api.matchain.io",
29 | Origin: "https://telegram.blum.codes",
30 | "User-Agent": this.device.userAgent,
31 | Referer: "https://telegram.blum.codes/",
32 | Connection: "keep-alive",
33 | "Sec-Fetch-Dest": "empty",
34 | };
35 | }
36 |
37 | updateToken(token) {
38 | this.token = token;
39 | }
40 |
41 | updateRefreshToken(token) {
42 | this.refreshToken = token;
43 | }
44 |
45 | updateConnect(status) {
46 | this.isConnected = status;
47 | }
48 |
49 | initConfig() {
50 | const headers = {
51 | ...this.headers,
52 | };
53 |
54 | if (this.token) {
55 | headers["Authorization"] = `Bearer ${this.token}`;
56 | }
57 | const config = {
58 | headers,
59 | };
60 | if (this.proxy && this.proxy !== "skip") {
61 | config["httpsAgent"] = new HttpsProxyAgent(this.proxy);
62 | }
63 | return config;
64 | }
65 |
66 | get(domain, endPoint) {
67 | const url = this.baseURL[domain] + endPoint;
68 | const config = this.initConfig();
69 | return axios.get(url, config);
70 | }
71 |
72 | // async get(domain, endPoint) {
73 | // const session = new TlsClient.Session({
74 | // clientIdentifier: "chrome_105",
75 | // });
76 |
77 | // const url = this.baseURL[domain] + endPoint;
78 | // const config = this.initConfig();
79 | // return session.get(url, {
80 | // headers: config.headers,
81 | // proxy: this.proxy,
82 | // });
83 | // }
84 |
85 | post(domain, endPoint, body) {
86 | const url = this.baseURL[domain] + endPoint;
87 | const config = this.initConfig();
88 | return axios.post(url, body, config);
89 | }
90 |
91 | // async post(domain, endPoint, body) {
92 | // const session = new TlsClient.Session({
93 | // clientIdentifier: "chrome_105",
94 | // });
95 |
96 | // const url = this.baseURL[domain] + endPoint;
97 | // const config = this.initConfig();
98 | // return session.post(url, {
99 | // headers: config.headers,
100 | // json: body,
101 | // proxy: this.proxy,
102 | // });
103 | // }
104 |
105 | put(domain, endPoint, body) {
106 | const url = this.baseURL[domain] + endPoint;
107 | const config = this.initConfig();
108 | return axios.put(url, body, config);
109 | }
110 |
111 | async checkProxyIP(lang) {
112 | if (!this.proxy || this.proxy === "skip") {
113 | this.log.updateIp("🖥️");
114 | return null;
115 | }
116 | try {
117 | const proxyAgent = new HttpsProxyAgent(this.proxy);
118 | const response = await axios.get("https://api.ipify.org?format=json", {
119 | httpsAgent: proxyAgent,
120 | });
121 | if (response.status === 200) {
122 | const ip = response.data.ip;
123 | this.log.updateIp(ip);
124 | return ip;
125 | } else {
126 | throw new Error(lang?.http?.error_proxy);
127 | }
128 | } catch (error) {
129 | this.log.updateIp("🖥️");
130 | return -1;
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "error_proxy": "Proxy error, check the proxy connection, will try to reconnect after 30s",
4 | "error_proxy_log": "Proxy connection error",
5 | "error_login_log": "Login failure error",
6 | "times": "times",
7 | "write_log_error": "Failed to log error",
8 | "skip_task_message": "Temporarily skipping task due to server error (will resume when the server stabilizes)",
9 | "skip_game_message": "Temporarily pausing the game due to a new update. It will reopen once the update is complete.",
10 | "copyright": "Tool developed and shared for free by ZuyDD",
11 | "copyright2": "Any act of selling this tool in any form is not allowed!",
12 | "update_guide": "Update the latest tools at:",
13 | "buy_key_info": "Buy or receive a free API KEY at:",
14 | "countdown_message": "All threads have run, need to wait:"
15 | },
16 | "http": {
17 | "error_proxy": "Proxy error, check the proxy connection"
18 | },
19 | "server": {
20 | "get_json_github_error": "Failed to fetch data from zuydd server",
21 | "noti": "System notification",
22 | "noti_new_version": "A new version is available",
23 | "download_now": "download here"
24 | },
25 | "user": {
26 | "not_found": "User data not found"
27 | },
28 | "auth": {
29 | "run_account": "Running account",
30 | "login_error_msg": "Login process failed, please check the account information (you may need to obtain a new query_id). The system will attempt to log in again after 60s",
31 | "login_success": "Login successful: ",
32 | "points": "Points:",
33 | "get_info_error_msg": "Failed to fetch account information",
34 | "refresh_token_error_msg": "Failed to refresh token",
35 | "login_error": "Login failed"
36 | },
37 | "daily": {
38 | "checked": "Checked in today",
39 | "checkin_success": "Check-in successful, reward",
40 | "checkin_failed": "Check-in failed",
41 | "game_turn": "game turn"
42 | },
43 | "tribe": {
44 | "join_tribe_success": "Successfully joined Tribe",
45 | "join_tribe_failed": "Failed to join tribe"
46 | },
47 | "task": {
48 | "get_task_error_msg": "Failed to retrieve task list",
49 | "task_completed": "All tasks completed",
50 | "tasks_remaining": "XXX tasks YYY remaining",
51 | "tasks_completed_skip": "All tasks XXX completed (excluding manual tasks skipped)",
52 | "do_task_error_msg": "Failed to complete task XXX",
53 | "task_not_answer": "Task XXX has no answer, retrying later",
54 | "do_task_success": "Task XXX completed successfully, reward",
55 | "claim_task_failed": "Failed to claim reward for task XXX",
56 | "retry_task_error": "Retrying failed tasks...",
57 | "start_task_parent_msg": "Starting task XXX, waiting for all subtasks to complete to claim reward",
58 | "task_done_msg": "Task completed",
59 | "task_failed_msg": "Task XXX failed",
60 | "not_completed_subtask": "All subtasks of the task are not yet completed"
61 | },
62 | "invite": {
63 | "get_info_failed": "Failed to retrieve invite balance information",
64 | "claim_success": "Successfully claimed referral points, received",
65 | "claim_failed": "Failed to claim referral points"
66 | },
67 | "game": {
68 | "can": "can",
69 | "notcan": "cannot",
70 | "claim_dogs": "claim DOGS",
71 | "game_remaining": "XXX game plays remaining",
72 | "no_api_key": "No API KEY, skipping game",
73 | "key_limit_used": "API KEY game play limit reached. Contact Telegram @zuydd to purchase more",
74 | "start_game_msg": "Starting game, finish and claim reward after",
75 | "start_game_failed": "Game failed",
76 | "claim_success": "Game completed, reward",
77 | "claim_failed": "Failed to claim game reward",
78 | "not_found_server_free": "No free servers available!",
79 | "create_payload_failed": "Failed to create payload",
80 | "used_turns": "All game turns used",
81 | "skip_play_game_msg": "Configured to skip game during this time, next play after",
82 | "minute": "minutes"
83 | },
84 | "key": {
85 | "key_invalid": "Invalid API KEY, contact Telegram @zuydd to receive/buy an API KEY",
86 | "key_remaining": "XXX uses remaining",
87 | "enter_key": "Enter your game API KEY? Leave blank if you don't have one (will skip game during tool operation)"
88 | },
89 | "farming": {
90 | "start_farming_msg": "Farming started, wait to claim later",
91 | "time_farming": "480 minutes",
92 | "start_farming_failed": "Failed to start farming",
93 | "claim_farming_success": "Farming claim successful, reward",
94 | "claim_farming_failed": "Farming claim failed",
95 | "countdown_farming_msg": "Claim time not reached, wait until later",
96 | "minute": "minutes"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/lang/vi.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "error_proxy": "Proxy lỗi, kiểm tra lại kết nối proxy, sẽ thử kết nối lại sau 30s",
4 | "error_proxy_log": "Lỗi kết nối proxy",
5 | "error_login_log": "Lỗi đăng nhập thất bại quá",
6 | "times": "lần",
7 | "write_log_error": "Ghi lỗi thất bại",
8 | "skip_task_message": "Tạm bỏ qua làm nhiệm vụ do lỗi server (sẽ tự động mở lại khi server ổn định)",
9 | "skip_game_message": "Tạm bỏ qua chơi game do có cập nhật mới, sẽ mở lại khi cập nhật xong",
10 | "copyright": "Tool phát triển và chia sẻ miễn phí bởi ZuyDD",
11 | "copyright2": "Mọi hành vi buôn bán tool dưới bất cứ hình thức nào đều không được cho phép!",
12 | "update_guide": "Cập nhật các tool mới nhất tại:",
13 | "buy_key_info": "Mua, nhận miễn phí API KEY tại:",
14 | "countdown_message": "Đã chạy hết các luồng, cần chờ:"
15 | },
16 | "http": {
17 | "error_proxy": "Proxy lỗi, kiểm tra lại kết nối proxy"
18 | },
19 | "server": {
20 | "get_json_github_error": "Lấy dữ liệu server zuydd thất bại",
21 | "noti": "Thông báo từ hệ thống",
22 | "noti_new_version": "Đã có phiên bản mới",
23 | "download_now": "tải ngay tại đây"
24 | },
25 | "user": {
26 | "not_found": "Không tìm thấy dữ liệu user"
27 | },
28 | "auth": {
29 | "run_account": "Chạy tài khoản",
30 | "login_error_msg": "Quá trình đăng nhập thất bại, vui lòng kiểm tra lại thông tin tài khoản (có thể cần phải lấy mới query_id). Hệ thống sẽ thử đăng nhập lại sau 60s",
31 | "login_success": "Đăng nhập thành công: ",
32 | "points": "Số điểm:",
33 | "get_info_error_msg": "Lấy thông tin tài khoản thất bại",
34 | "refresh_token_error_msg": "Refresh token thất bại",
35 | "login_error": "Đăng nhập thất bại"
36 | },
37 | "daily": {
38 | "checked": "Đã checkin hôm nay",
39 | "checkin_success": "Checkin thành công, phần thưởng",
40 | "checkin_failed": "Checkin thất bại",
41 | "game_turn": "lượt chơi game"
42 | },
43 | "tribe": {
44 | "join_tribe_success": "Tham gia thành công Tribe",
45 | "join_tribe_failed": "Tham gia tribe thất bại"
46 | },
47 | "task": {
48 | "get_task_error_msg": "Lấy danh sách nhiệm vụ thất bại",
49 | "task_completed": "Đã làm hết nhiệm vụ",
50 | "tasks_remaining": "Còn XXX nhiệm vụ YYY chưa hoàn thành",
51 | "tasks_completed_skip": "Đã làm hết các nhiệm vụ XXX (trừ các nhiệm phải làm tay bị bỏ qua)",
52 | "do_task_error_msg": "Làm nhiệm vụ XXX thất bại",
53 | "task_not_answer": "Nhiệm vụ XXX chưa có câu trả lời, chờ làm lại sau",
54 | "do_task_success": "Làm nhiệm vụ XXX thành công, phần thưởng",
55 | "claim_task_failed": "Claim phần thưởng nhiệm vụ XXX thất bại",
56 | "retry_task_error": "Chạy lại các nhiệm vụ bị lỗi...",
57 | "start_task_parent_msg": "Bắt đầu làm nhiệm vụ XXX, chờ hoàn thành hết các nhiệm vụ con để nhận thưởng",
58 | "task_done_msg": "Đã hoàn thành nhiệm vụ",
59 | "task_failed_msg": "Làm nhiệm vụ XXX thất bại",
60 | "not_completed_subtask": "Chưa hoàn thành hết các nhiệm vụ con của task"
61 | },
62 | "invite": {
63 | "get_info_failed": "Lấy thông tin invite balance thất bại",
64 | "claim_success": "Claim điểm giới thiệu thành công, nhận được",
65 | "claim_failed": "Claim điểm giới thiệu thất bại"
66 | },
67 | "game": {
68 | "can": "có thể",
69 | "notcan": "không thể",
70 | "claim_dogs": "nhặt DOGS",
71 | "game_remaining": "Còn XXX lượt chơi game",
72 | "no_api_key": "Không có API KEY, bỏ qua chơi game",
73 | "key_limit_used": "Đã dùng hết lượt chơi game của API KEY. Liên hệ Telegram @zuydd để mua thêm",
74 | "start_game_msg": "Bắt đầu chơi game, kết thúc và nhận thưởng sau",
75 | "start_game_failed": "Chơi game thất bại",
76 | "claim_success": "Chơi game xong, phần thưởng",
77 | "claim_failed": "Nhận thưởng chơi game thất bại",
78 | "not_found_server_free": "Không còn máy chủ miễn phí nào hoạt động!",
79 | "create_payload_failed": "Tạo payload thất bại",
80 | "used_turns": "Đã dùng hết lượt chơi game",
81 | "skip_play_game_msg": "Đã cài đặt không thể chơi game trong khoảng thời gian này, lần chơi tiếp theo sau",
82 | "minute": "phút"
83 | },
84 | "key": {
85 | "key_invalid": "API KEY không hợp lệ, liên hệ Telegram @zuydd để nhận/mua API KEY",
86 | "key_remaining": "Còn XXX lượt sử dụng",
87 | "enter_key": "Nhập API KEY chơi game của bạn? Để trống nếu bạn không có (sẽ bỏ qua chơi game trong quá trình chạy tool)"
88 | },
89 | "farming": {
90 | "start_farming_msg": "Đã bắt đầu farming, chờ claim sau",
91 | "time_farming": "480 phút",
92 | "start_farming_failed": "Bắt đầu farming thất bại",
93 | "claim_farming_success": "Claim farming thành công, phần thưởng",
94 | "claim_farming_failed": "Claim farming thất bại",
95 | "countdown_farming_msg": "Chưa tới thời gian claim, chờ sau",
96 | "minute": "phút"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/lang/id.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "error_proxy": "Proxy error, periksa kembali koneksi proxy, akan mencoba menghubungkan kembali setelah 30 detik",
4 | "error_proxy_log": "Kesalahan koneksi proxy",
5 | "error_login_log": "Kesalahan login gagal",
6 | "times": "kali",
7 | "write_log_error": "Gagal mencatat kesalahan",
8 | "skip_task_message": "Sementara melewati tugas karena kesalahan server (akan otomatis melanjutkan saat server stabil)",
9 | "skip_game_message": "Sementara menghentikan permainan karena ada pembaruan baru. Akan dibuka kembali setelah pembaruan selesai.",
10 | "copyright": "Alat ini dikembangkan dan dibagikan gratis oleh ZuyDD",
11 | "copyright2": "Setiap tindakan menjual alat dalam bentuk apa pun tidak diizinkan!",
12 | "update_guide": "Perbarui alat terbaru di:",
13 | "buy_key_info": "Beli atau dapatkan API KEY gratis di:",
14 | "countdown_message": "Semua alur telah selesai, tunggu:"
15 | },
16 | "http": {
17 | "error_proxy": "Proxy error, periksa kembali koneksi proxy"
18 | },
19 | "server": {
20 | "get_json_github_error": "Gagal mengambil data dari server zuydd",
21 | "noti": "Pemberitahuan dari sistem",
22 | "noti_new_version": "Versi baru tersedia",
23 | "download_now": "unduh di sini"
24 | },
25 | "user": {
26 | "not_found": "Data pengguna tidak ditemukan"
27 | },
28 | "auth": {
29 | "run_account": "Menjalankan akun",
30 | "login_error_msg": "Proses login gagal, silakan periksa informasi akun (mungkin perlu mendapatkan query_id baru). Sistem akan mencoba login kembali dalam 60 detik",
31 | "login_success": "Login berhasil: ",
32 | "points": "Jumlah poin:",
33 | "get_info_error_msg": "Gagal mengambil informasi akun",
34 | "refresh_token_error_msg": "Gagal memperbarui token",
35 | "login_error": "Login gagal"
36 | },
37 | "daily": {
38 | "checked": "Sudah check-in hari ini",
39 | "checkin_success": "Check-in berhasil, hadiah",
40 | "checkin_failed": "Check-in gagal",
41 | "game_turn": "giliran bermain game"
42 | },
43 | "tribe": {
44 | "join_tribe_success": "Berhasil bergabung dengan Tribe",
45 | "join_tribe_failed": "Gagal bergabung dengan Tribe"
46 | },
47 | "task": {
48 | "get_task_error_msg": "Gagal mengambil daftar tugas",
49 | "task_completed": "Semua tugas telah selesai",
50 | "tasks_remaining": "Masih ada XXX tugas YYY yang belum selesai",
51 | "tasks_completed_skip": "Semua tugas XXX telah selesai (kecuali tugas manual yang dilewati)",
52 | "do_task_error_msg": "Gagal menjalankan tugas XXX",
53 | "task_not_answer": "Tugas XXX belum ada jawabannya, coba lagi nanti",
54 | "do_task_success": "Tugas XXX berhasil diselesaikan, hadiah",
55 | "claim_task_failed": "Gagal klaim hadiah untuk tugas XXX",
56 | "retry_task_error": "Mengulangi tugas yang gagal...",
57 | "start_task_parent_msg": "Memulai tugas XXX, tunggu hingga semua sub-tugas selesai untuk klaim hadiah",
58 | "task_done_msg": "Tugas selesai",
59 | "task_failed_msg": "Tugas XXX gagal",
60 | "not_completed_subtask": "Belum semua sub-tugas dari tugas selesai"
61 | },
62 | "invite": {
63 | "get_info_failed": "Gagal mengambil informasi saldo undangan",
64 | "claim_success": "Klaim poin referensi berhasil, diterima",
65 | "claim_failed": "Gagal klaim poin referensi"
66 | },
67 | "game": {
68 | "can": "dapat",
69 | "notcan": "tidak dapat",
70 | "claim_dogs": "ambil DOGS",
71 | "game_remaining": "Tersisa XXX giliran bermain game",
72 | "no_api_key": "Tidak ada API KEY, melewati permainan",
73 | "key_limit_used": "Batas giliran bermain game untuk API KEY telah habis. Hubungi Telegram @zuydd untuk membeli lebih lanjut",
74 | "start_game_msg": "Memulai game, akan selesai dan klaim hadiah setelah",
75 | "start_game_failed": "Permainan gagal",
76 | "claim_success": "Permainan selesai, hadiah",
77 | "claim_failed": "Gagal klaim hadiah permainan",
78 | "not_found_server_free": "Tidak ada server gratis yang berfungsi!",
79 | "create_payload_failed": "Gagal membuat payload",
80 | "used_turns": "Semua giliran bermain game telah habis",
81 | "skip_play_game_msg": "Diatur untuk tidak bermain dalam waktu ini, giliran berikutnya setelah",
82 | "minute": "menit"
83 | },
84 | "key": {
85 | "key_invalid": "API KEY tidak valid, hubungi Telegram @zuydd untuk mendapatkan/membeli API KEY",
86 | "key_remaining": "Tersisa XXX penggunaan",
87 | "enter_key": "Masukkan API KEY untuk bermain game? Biarkan kosong jika tidak punya (akan melewati permainan saat alat dijalankan)"
88 | },
89 | "farming": {
90 | "start_farming_msg": "Farming dimulai, klaim nanti",
91 | "time_farming": "480 menit",
92 | "start_farming_failed": "Gagal memulai farming",
93 | "claim_farming_success": "Klaim farming berhasil, hadiah",
94 | "claim_farming_failed": "Klaim farming gagal",
95 | "countdown_farming_msg": "Belum waktu untuk klaim, tunggu",
96 | "minute": "menit"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/lang/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "error_proxy": "Ошибка прокси, проверьте подключение прокси, повторная попытка подключения через 30 секунд",
4 | "error_proxy_log": "Ошибка подключения прокси",
5 | "error_login_log": "Ошибка входа",
6 | "times": "раз",
7 | "write_log_error": "Не удалось записать ошибку",
8 | "skip_task_message": "Задание временно пропущено из-за ошибки сервера (автоматически возобновится, когда сервер стабилизируется)",
9 | "skip_game_message": "Временно приостанавливаем игру из-за нового обновления. Игра будет доступна после завершения обновления.",
10 | "copyright": "Инструмент разработан и бесплатно распространяется ZuyDD",
11 | "copyright2": "Любая продажа этого инструмента в любой форме запрещена!",
12 | "update_guide": "Обновите последние инструменты здесь:",
13 | "buy_key_info": "Купить или получить бесплатный API KEY здесь:",
14 | "countdown_message": "Все потоки завершены, требуется ожидание:"
15 | },
16 | "http": {
17 | "error_proxy": "Ошибка прокси, проверьте подключение прокси"
18 | },
19 | "server": {
20 | "get_json_github_error": "Не удалось получить данные с сервера zuydd",
21 | "noti": "Системное уведомление",
22 | "noti_new_version": "Доступна новая версия",
23 | "download_now": "скачать здесь"
24 | },
25 | "user": {
26 | "not_found": "Данные пользователя не найдены"
27 | },
28 | "auth": {
29 | "run_account": "Запуск аккаунта",
30 | "login_error_msg": "Ошибка входа, пожалуйста, проверьте учетные данные (возможно, потребуется новый query_id). Система повторит попытку входа через 60 секунд",
31 | "login_success": "Успешный вход: ",
32 | "points": "Очки:",
33 | "get_info_error_msg": "Не удалось получить данные аккаунта",
34 | "refresh_token_error_msg": "Не удалось обновить токен",
35 | "login_error": "Ошибка входа"
36 | },
37 | "daily": {
38 | "checked": "Сегодня уже выполнена регистрация",
39 | "checkin_success": "Регистрация успешна, награда",
40 | "checkin_failed": "Не удалось выполнить регистрацию",
41 | "game_turn": "игровой ход"
42 | },
43 | "tribe": {
44 | "join_tribe_success": "Успешно присоединились к Tribe",
45 | "join_tribe_failed": "Не удалось присоединиться к Tribe"
46 | },
47 | "task": {
48 | "get_task_error_msg": "Не удалось получить список задач",
49 | "task_completed": "Все задачи выполнены",
50 | "tasks_remaining": "Осталось XXX задач YYY невыполненными",
51 | "tasks_completed_skip": "Все задачи XXX выполнены (за исключением пропущенных ручных)",
52 | "do_task_error_msg": "Ошибка выполнения задачи XXX",
53 | "task_not_answer": "Задача XXX не имеет ответа, повторите позже",
54 | "do_task_success": "Задача XXX успешно выполнена, награда",
55 | "claim_task_failed": "Ошибка получения награды за задачу XXX",
56 | "retry_task_error": "Повтор выполнения ошибок задач...",
57 | "start_task_parent_msg": "Начало выполнения задачи XXX, дождитесь завершения всех подзадач для получения награды",
58 | "task_done_msg": "Задача выполнена",
59 | "task_failed_msg": "Ошибка выполнения задачи XXX",
60 | "not_completed_subtask": "Не все подзадачи выполнены"
61 | },
62 | "invite": {
63 | "get_info_failed": "Не удалось получить информацию о балансе приглашений",
64 | "claim_success": "Успешное получение очков приглашений, получено",
65 | "claim_failed": "Не удалось получить очки приглашений"
66 | },
67 | "game": {
68 | "can": "можно",
69 | "notcan": "нельзя",
70 | "claim_dogs": "собрать DOGS",
71 | "game_remaining": "Осталось XXX игровых ходов",
72 | "no_api_key": "Нет API KEY, пропуск игры",
73 | "key_limit_used": "Лимит игровых ходов для API KEY исчерпан. Свяжитесь с Telegram @zuydd для покупки",
74 | "start_game_msg": "Начало игры, завершение и получение награды позже",
75 | "start_game_failed": "Ошибка в игре",
76 | "claim_success": "Игра завершена, награда",
77 | "claim_failed": "Ошибка получения награды за игру",
78 | "not_found_server_free": "Нет доступных бесплатных серверов!",
79 | "create_payload_failed": "Не удалось создать payload",
80 | "used_turns": "Все игровые ходы использованы",
81 | "skip_play_game_msg": "Настроено пропускать игру в это время, следующий ход через",
82 | "minute": "минут"
83 | },
84 | "key": {
85 | "key_invalid": "Недействительный API KEY, свяжитесь с Telegram @zuydd для получения/покупки API KEY",
86 | "key_remaining": "Осталось XXX использований",
87 | "enter_key": "Введите API KEY для игры? Оставьте пустым, если его нет (пропустит игру при запуске инструмента)"
88 | },
89 | "farming": {
90 | "start_farming_msg": "Начало farming, ожидайте для получения награды",
91 | "time_farming": "480 минут",
92 | "start_farming_failed": "Не удалось начать farming",
93 | "claim_farming_success": "Успешное получение награды за farming",
94 | "claim_farming_failed": "Ошибка получения награды за farming",
95 | "countdown_farming_msg": "Время получения награды не наступило, подождите",
96 | "minute": "минут"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/services/auth.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 | import fileHelper from "../helpers/file.js";
3 | import tokenHelper from "../helpers/token.js";
4 |
5 | class AuthService {
6 | constructor() {}
7 |
8 | async login(user, lang, skipLog = false) {
9 | user.http.updateToken(null);
10 | user.http.updateRefreshToken(null);
11 | const body = {
12 | query: user.query_id,
13 | referralToken: user?.database?.ref,
14 | };
15 | try {
16 | const { data } = await user.http.post(
17 | 3,
18 | "auth/provider/PROVIDER_TELEGRAM_MINI_APP",
19 | body
20 | );
21 | if (data?.token) {
22 | return {
23 | access: data?.token?.access,
24 | refresh: data?.token?.refresh,
25 | };
26 | }
27 | return null;
28 | } catch (error) {
29 | if (!skipLog) {
30 | user.log.logError(
31 | `${lang?.auth?.login_error}: ${error.response?.data?.message}`
32 | );
33 | }
34 | return null;
35 | }
36 | }
37 |
38 | async refresh(user, lang, refreshToken, skipLog = false) {
39 | user.http.updateToken(null);
40 | user.http.updateRefreshToken(null);
41 | const body = {
42 | refresh: refreshToken,
43 | };
44 | try {
45 | const { data } = await user.http.post(1, "auth/refresh", body);
46 | if (data?.access) {
47 | return {
48 | access: data.access,
49 | refresh: data.refresh,
50 | };
51 | }
52 | return null;
53 | } catch (error) {
54 | if (!skipLog) {
55 | user.log.logError(
56 | `${lang?.auth?.refresh_token_error_msg}: ${error.response?.data?.message}`
57 | );
58 | }
59 | return null;
60 | }
61 | }
62 |
63 | async handleLogin(user, lang) {
64 | console.log(
65 | `============== ${lang?.auth?.run_account} ${user.index} | ${user.info.fullName.green} ==============`
66 | );
67 |
68 | let info = null;
69 | let token = fileHelper.getTokenById(user.info.id);
70 |
71 | if (token && !tokenHelper.isExpired(token.access)) {
72 | const info = {
73 | access: token.access,
74 | refresh: token.refresh,
75 | };
76 | const profile = await this.handleAfterLogin(user, lang, info);
77 | return {
78 | status: 1,
79 | profile,
80 | };
81 | }
82 |
83 | let infoLogin = await this.login(user, lang);
84 |
85 | if (infoLogin) {
86 | const profile = await this.handleAfterLogin(user, lang, infoLogin);
87 | return {
88 | status: 1,
89 | profile,
90 | };
91 | }
92 | user.log.logError(lang?.auth?.login_error_msg);
93 | return {
94 | status: 0,
95 | profile: null,
96 | };
97 | }
98 |
99 | async getProfile(user, lang) {
100 | try {
101 | const { data } = await user.http.get(0, "user/balance");
102 | if (data) {
103 | return data;
104 | }
105 | return null;
106 | } catch (error) {
107 | user.log.logError(
108 | `${lang?.auth?.get_info_error_msg}: ${error.response?.data?.message}`
109 | );
110 | return null;
111 | }
112 | }
113 |
114 | async reconnect(user, lang) {
115 | let info = null;
116 | let token = fileHelper.getTokenById(user.info.id);
117 |
118 | if (
119 | token &&
120 | tokenHelper.isExpired(token.access) &&
121 | !tokenHelper.isExpired(token.refresh)
122 | ) {
123 | info = await this.refresh(user, lang, token.refresh, true);
124 | if (info) {
125 | await this.handleAfterReconnect(user, info);
126 | return 1;
127 | }
128 | }
129 |
130 | let infoLogin = await this.login(user, lang, true);
131 |
132 | if (infoLogin) {
133 | await this.handleAfterReconnect(user, infoLogin);
134 | return 1;
135 | }
136 | // user.log.logError(
137 | // "Quá trình kết nối lại thất bại, vui lòng kiểm tra lại thông tin tài khoản (có thể cần phải lấy mới query_id)"
138 | // );
139 | return 0;
140 | }
141 |
142 | async handleAfterLogin(user, lang, info) {
143 | const accessToken = info.access || null;
144 | const refreshToken = info.refresh || null;
145 | user.http.updateToken(accessToken);
146 | user.http.updateRefreshToken(refreshToken);
147 | fileHelper.saveToken(user.info.id, info);
148 | const profile = await this.getProfile(user, lang);
149 | if (profile) {
150 | user.log.log(
151 | colors.green(lang?.auth?.login_success) +
152 | `${lang?.auth?.points} ${
153 | colors.green(Math.round(profile?.availableBalance)) + user.currency
154 | }`
155 | );
156 | if (!user.http.isConnected) {
157 | user.http.updateConnect(true);
158 | setInterval(async () => {
159 | await this.reconnect(user, lang);
160 | }, 60 * 1000 * 20);
161 | }
162 | }
163 | return profile;
164 | }
165 |
166 | async handleAfterReconnect(user, info) {
167 | const accessToken = info.access || null;
168 | const refreshToken = info.refresh || null;
169 | user.http.updateToken(accessToken);
170 | user.http.updateRefreshToken(refreshToken);
171 | fileHelper.saveToken(user.info.id, info);
172 | // const profile = await this.getProfile(user);
173 | // if (profile) {
174 | // user.log.logSuccess("Kết nối lại thành công");
175 | // }
176 | }
177 | }
178 |
179 | const authService = new AuthService();
180 | export default authService;
181 |
--------------------------------------------------------------------------------
/src/services/game.js:
--------------------------------------------------------------------------------
1 | import colors from "colors";
2 | import dayjs from "dayjs";
3 | import timezone from "dayjs/plugin/timezone.js";
4 | import utc from "dayjs/plugin/utc.js";
5 | import delayHelper from "../helpers/delay.js";
6 | import generatorHelper from "../helpers/generator.js";
7 | import { pack, proof } from "../helpers/payload-blum.js";
8 | import authService from "./auth.js";
9 | dayjs.extend(utc);
10 | dayjs.extend(timezone);
11 |
12 | class GameService {
13 | constructor() {
14 | this.API_KEY = "";
15 | this.REMAINING_QUOTA = 99999;
16 | }
17 |
18 | setApiKey(apiKey) {
19 | this.API_KEY = apiKey;
20 | }
21 |
22 | setQuota(quota) {
23 | this.REMAINING_QUOTA = quota;
24 | }
25 |
26 | async playGame(user, lang, delay) {
27 | try {
28 | const { data } = await user.http.post(5, "game/play", {});
29 | if (data.message === "another game in progress") {
30 | return 3;
31 | }
32 |
33 | if (data) {
34 | user.log.log(
35 | `${lang?.game?.start_game_msg}: ${colors.blue(delay + "s")}`
36 | );
37 | return data.gameId;
38 | } else {
39 | throw new Error(`${lang?.game?.start_game_failed}: ${data.message}`);
40 | }
41 | } catch (error) {
42 | if (error.response?.data?.message === "not enough play passes") {
43 | return 2;
44 | } else {
45 | user.log.logError(
46 | `${lang?.game?.start_game_failed}: ${error.response?.data?.message}`
47 | );
48 | }
49 | return null;
50 | }
51 | }
52 |
53 | async claimGame(user, lang, gameId) {
54 | const randomPoints = user?.database?.randomPoints || [150, 190];
55 | const multiplierPoint = user?.database?.multiplierPoint || 1;
56 | let points = generatorHelper.randomInt(randomPoints[0], randomPoints[1]);
57 | const payload = await this.createPlayload(user, lang, gameId, points);
58 | if (!payload) return;
59 |
60 | const body = { payload };
61 | try {
62 | const { text, data } = await user.http.post(5, "game/claim", body);
63 |
64 | if (data === "OK") {
65 | user.log.log(
66 | `${lang?.game?.claim_success}: ${colors.green(
67 | points * multiplierPoint + user.currency
68 | )}`
69 | );
70 | return true;
71 | } else {
72 | throw new Error(`${lang?.game?.claim_failed}: ${data?.message}`);
73 | }
74 | } catch (error) {
75 | user.log.logError(
76 | `${lang?.game?.claim_failed}: ${error.response?.data?.message}`
77 | );
78 | return false;
79 | }
80 | }
81 |
82 | async createPlayload(user, lang, gameId, points) {
83 | const resultProof = await proof(gameId);
84 | const MULTIPLIER_POINT = user?.database?.multiplierPoint || 1;
85 |
86 | const challenge = {
87 | id: generatorHelper.uuid(),
88 | ...resultProof,
89 | };
90 |
91 | const earnedAssets = {
92 | CLOVER: {
93 | clicks: parseInt(points),
94 | },
95 | FREEZE: {
96 | clicks: generatorHelper.randomInt(0, 2),
97 | },
98 | BOMB: {
99 | clicks: 0,
100 | },
101 | };
102 |
103 | const totalPoints = {
104 | BP: {
105 | amount: parseInt(points) * MULTIPLIER_POINT,
106 | },
107 | };
108 |
109 | const resultPack = await pack(gameId, challenge, totalPoints, earnedAssets);
110 |
111 | const payload = resultPack;
112 | return payload;
113 | }
114 |
115 | checkTimePlayGame(time) {
116 | // Lấy giờ hiện tại theo múi giờ Việt Nam (UTC+7)
117 | const nowHour = dayjs().hour();
118 | return !time.includes(nowHour);
119 | }
120 |
121 | getMinutesUntilNextStart(times) {
122 | // Lấy giờ hiện tại theo múi giờ Việt Nam (UTC+7)
123 | const currentHour = dayjs().hour();
124 | times.sort((a, b) => a - b);
125 |
126 | let nextHour = currentHour + 1;
127 |
128 | while (times.includes(nextHour)) {
129 | nextHour++;
130 | }
131 |
132 | const now = dayjs();
133 |
134 | const nextStartTime = now
135 | .set("hour", nextHour)
136 | .set("minute", 0)
137 | .set("second", 0);
138 |
139 | // Tính số phút từ giờ hiện tại đến lần bắt đầu tiếp theo
140 | return nextStartTime.diff(now, "minute");
141 | }
142 |
143 | async handleGame(user, lang, playPasses, timePlayGame) {
144 | const isInTimeRange = this.checkTimePlayGame(timePlayGame);
145 | if (isInTimeRange) {
146 | const profile = await authService.getProfile(user, lang);
147 | if (profile) playPasses = profile?.playPasses;
148 | const msg = lang?.game?.game_remaining.replace(
149 | "XXX",
150 | colors.blue(playPasses)
151 | );
152 | user.log.log(`${msg}`);
153 | let gameCount = playPasses || 0;
154 | let errorCount = 0;
155 | while (gameCount > 0) {
156 | if (errorCount > 3) {
157 | gameCount = 0;
158 | continue;
159 | }
160 | // if (!this.API_KEY) {
161 | // user.log.log(colors.yellow(lang?.game?.no_api_key));
162 | // gameCount = 0;
163 | // continue;
164 | // }
165 | if (this.REMAINING_QUOTA <= 0) {
166 | user.log.log(colors.yellow(lang?.game?.key_limit_used));
167 | gameCount = 0;
168 | continue;
169 | }
170 | await delayHelper.delay(2);
171 | const delay = 30 + generatorHelper.randomInt(5, 10);
172 | const gameId = await this.playGame(user, lang, delay);
173 | if (gameId === 2) {
174 | gameCount = 0;
175 | continue;
176 | }
177 | if (gameId === 3) {
178 | user.log.log(
179 | colors.yellow(
180 | "Đang trong một lượt chơi khác vui lòng chờ và thử lại"
181 | )
182 | );
183 | await delayHelper.delay(180);
184 | continue;
185 | }
186 | if (gameId) {
187 | errorCount = 0;
188 |
189 | await delayHelper.delay(delay);
190 | const statusClaim = await this.claimGame(user, lang, gameId);
191 | if (!statusClaim) {
192 | errorCount++;
193 | }
194 | if (statusClaim) gameCount--;
195 | } else {
196 | errorCount++;
197 | }
198 | }
199 | if (playPasses > 0) user.log.log(colors.magenta(lang?.game?.used_turns));
200 | return -1;
201 | } else {
202 | const minutesUntilNextStart = this.getMinutesUntilNextStart(timePlayGame);
203 | user.log.log(
204 | colors.yellow(
205 | `${lang?.game?.skip_play_game_msg}: ${colors.blue(
206 | minutesUntilNextStart + ` ${lang?.game?.minute}`
207 | )}`
208 | )
209 | );
210 | return minutesUntilNextStart;
211 | }
212 | }
213 | }
214 |
215 | const gameService = new GameService();
216 | export default gameService;
217 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tool Auto Blum NodeJS by ZuyDD
2 |
3 | **Tool developed and shared for free by ZuyDD**
4 |
5 |
6 |
7 |
8 | > [!WARNING]
9 | > Selling this tool in any form is not permitted!
10 |
11 | ## Purchase or receive a free API KEY at https://zuy-web.vercel.app/blum
12 |
13 | ## 🛠️ Installation Guide
14 |
15 | > NodeJS is required to be installed
16 |
17 | - Step 1: Download the latest version of the tool [here ⬇️](https://github.com/zuydd/blum/archive/refs/heads/main.zip)
18 | - Step 2: Extract the tool
19 | - Step 3: In the tool's directory (the folder containing `package.json`), run the command `npm install` to install the necessary libraries.
20 |
21 | ## 💾 How to Add Account Data
22 |
23 | > The tool supports both `user` and `query_id`
24 |
25 | > All data you need to input is in files located in the folder 📁 `src / data`
26 |
27 | - [users.txt](src/data/users.txt) : Contains a list of `user` or `query_id` for the accounts, each line represents an account.
28 | - [proxy.txt](src/data/proxy.txt) : Contains a list of proxies, each line corresponds to the account in the `users.txt` file. Leave empty if not using a proxy.
29 | - [token.json](src/data/token.json) : Contains the token list generated from `user` or `query_id`. Tokens will be automatically generated when you run the tool.
30 |
31 | > Proxy format: http://user:pass@ip:port
32 |
33 | ### Installation Commands
34 |
35 | 1. **Clone the repository:**
36 | ```bash
37 | git clone https://github.com/candyburst/blum.git
38 | ```
39 |
40 | 2. **Navigate into the project directory:**
41 | ```bash
42 | cd blum
43 | ```
44 |
45 | 3. **Install the required dependencies:**
46 | ```bash
47 | npm install
48 | ```
49 |
50 | **Note:** If you encounter problems during `npm install`, try this command:
51 | ```bash
52 | npm install --ignore-scripts --no-bin-links
53 | ```
54 |
55 | 4. **Edit the `users.txt` file to add queries or user info:**
56 | ```bash
57 | nano src/data/users.txt
58 | ```
59 |
60 | Add the required information in the file.
61 |
62 | - To exit nano, use the following key sequence:
63 | - `Ctrl + X`
64 | - `Y` (to confirm save)
65 | - `Ctrl + M` (to return)
66 |
67 | 5. **Start the application:**
68 | ```bash
69 | npm run start
70 | ```
71 | ## How to update and apply changes incase you face any error:
72 | **To update to the latest version:**
73 | ```bash
74 | git pull
75 | ```
76 |
77 | **If you encounter problems during `git pull`, try this command:**
78 | ```bash
79 | mv src/data/users.txt src/data/user.txt && git stash && git pull && mv src/data/user.txt src/data/users.txt
80 | ```
81 | # Important Note: You need to enter the pro key in the bot screen. Make sure you have the backup of the key.
82 |
83 | ## 🔄 Guide to Upgrade to the Latest Version When Facing Library Issues
84 |
85 | - **Step 1:** Download the latest version [here ⬇️](https://github.com/zuydd/blum/archive/refs/heads/main.zip)
86 | - **Step 2:** In the old tool directory, copy the [data](src/data) folder to another location to preserve the data (to avoid re-importing/re-creating).
87 | - **Step 3:** Extract the downloaded file.
88 | - **Step 4:** In the new tool directory (from the extracted file), copy the [src](src) folder and paste it over the `src` folder in the old tool directory (this updates the code).
89 | - **Step 5:** Copy back the `data` folder (from Step 2) and paste it over the `data` folder in the old tool directory.
90 |
91 | At this point, the old tool has been updated to the latest version and can be used as usual.
92 |
93 | Video tutorial: https://youtu.be/nwyrdEVxvPQ
94 |
95 | ## 🕹️ Features of the Tool
96 |
97 | - Automatic daily check-in
98 | - Automatically join tribes to earn an additional 10% bonus points
99 | - Automatically completes tasks
100 | - Automatically farms/claims rewards when it's time
101 | - Automatically plays games (requires API KEY, purchase or receive a free API KEY at https://zuy-web.vercel.app/blum)
102 | - Claims invite points
103 | - Auto-detects and reconnects proxies when there are errors. Add the proxy to the `proxy.txt` file corresponding to the account line. Leave empty or type "skip" for accounts without proxy.
104 | - Multi-thread support: run as many accounts as you want without blocking each other
105 | - Set game-playing times: the default is to always play games. If you want to avoid high-traffic periods, you can find the `TIME_PLAY_GAME = []` variable and input the hours you want to skip playing, e.g., entering [1, 2, 3, 8, 20] will skip game time during those hours.
106 |
107 | > [!WARNING]
108 | >
109 | > - Login, task, or game errors are due to Blum's server issues, not tool errors. Just let it be, and the tool will resume once the server is fixed.
110 | > - The server often experiences errors between 2 PM - 12 AM, so it's recommended to run the tool for the first time between 4 AM - 12 PM for smoother performance.
111 |
112 | ## 🌐 Language Settings
113 |
114 | - To change the tool's language, locate the variable `LANGUAGE = "vi"` in the [index.js](src/run/index.js) file and update it with the appropriate language code.
115 | - Supported languages:
116 | - `vi`: Vietnamese
117 | - `en`: English
118 | - `ru`: Russian
119 | - `id`: Indonesian
120 | - `zh`: Chinese
121 |
122 | ## ♾ Multi-Thread Setup
123 |
124 | - The tool will automatically run in multi-thread mode according to the number of accounts entered; no additional setup is required.
125 | - By default, in the first loop, each account (thread) will run 30 seconds apart to avoid spamming requests. You can adjust this by finding the `DELAY_ACC = 10` variable in the [index.js](src/run/index.js) file.
126 |
127 | ## ❌ Retry Mode for Errors
128 |
129 | - For proxy connection errors, the system will retry every 30 seconds. You can set a retry limit by finding the `MAX_RETRY_PROXY = 20` variable in the [index.js](src/run/index.js) file (default is 20). Once the retry limit is exceeded, the tool will stop auto operations for that account and log the error in [log.error.txt](src/data/log.error.txt).
130 | - For login failure errors, the system will retry every 60 seconds. You can set a retry limit by finding the `MAX_RETRY_LOGIN = 20` variable in the [index.js](src/run/index.js) file (default is 20). Once the retry limit is exceeded, the tool will stop auto operations for that account and log the error in [log.error.txt](src/data/log.error.txt).
131 |
132 | ## 🔄 Update History
133 |
134 | > When updating to a new version, simply copy the 📁 [data](src/data) folder from the old version to the new version and it will run without needing to re-enter the data.
135 |
136 | > Latest version: `v0.2.2`
137 |
138 |
v0.2.2 - 📅 10/30/2024
140 |
141 | - Randomize server
142 | - Add language options
143 | v0.2.1 - 📅 10/23/2024
147 |
148 | - Changed the amount of DOGS received each time a game is played.
149 | v0.2.0 - 📅 10/23/2024
152 |
153 | - Fixed the issue where the game could not be skipped when there was no API KEY.
154 | v0.1.9 - 📅 10/23/2024
158 |
159 | - Fix tool stopping bug
160 | v0.1.8 - 📅 10/23/2024
163 |
164 | - Added fake device feature
165 | - Added API KEY system for playing games
166 |
179 |
180 |