Auto Mining Notifier

Tự động kiểm tra và thông báo kết quả khai thác

// ==UserScript==
// @name         Auto Mining Notifier
// @namespace    http://tampermonkey.net/
// @version      2.7
// @description  Tự động kiểm tra và thông báo kết quả khai thác
// @match        https://cmangax2.com/*
// @grant        GM.xmlHttpRequest
// @grant        GM_notification
// @connect      api.telegram.org
// @connect      cmangax2.com
// @connect      discord.com
// ==/UserScript==

(function() {
    'use strict';
    const priorityItem = {
        "pet_heart_bag": "Túi thú tâm",
        "add_option": "Tinh Luyện Châu",
        "job_exp_3": "Thông Thạo Quyển Lv3",
        "job_exp_2": "Thông Thạo Quyển Lv2",
        "job_exp_1": "Thông Thạo Quyển Lv1",
        "medicinal_exp_2": "Tăng Ích Đan Lv2",
        "medicinal_exp_3": "Tăng Ích Đan Lv3",
        "medicinal_exp_4": "Tăng Ích Đan Lv4",
        "equipment_upgrade_2": "Trung phẩm Thiên Mộc Thạch",
        "egg_super_fragment": "Mảnh trứng thần thú",
        "egg_rare": "Trứng hiếm",
        "pet_exp_chest" : "Rương thú đan"
    };

    const priceInMarket = {
        "pet_heart_bag": 100,
        "add_option": 25,
        "job_exp_3": 3,
        "job_exp_2": 0.4,
        "job_exp_1": 0.1,
        "medicinal_exp_1": 1.2,
        "medicinal_exp_2": 2.4,
        "medicinal_exp_3": 5,
        "medicinal_exp_4": 8,
        "equipment_upgrade_2": 25,
        "egg_super_fragment": 5,
        "egg_rare": 35,
        "pet_exp_chest": 5
    };
    const RARE_COLORS = {
        4: { name: '🔥 Truyền Thuyết', color: '#ff0000' },
        3: { name: '📜 Sử Thi', color: '#c700ff' },
        2: { name: '🛡️ Hiếm', color: '#0099ff' },
        1: { name: '⚔️ Thường', color: '#666666' },
        0: { name: '❌ Không xác định', color: '#000000' }
    };

    const NOTIFICATION_CONFIG = {
        TELEGRAM: {
            token: 'Thay bằng token bằng cách tìm botfather rồi nhập /mybots rồi chọn token',
            chatId: 'Thay bằng chat id bằng cách tìm userinfo rồi start là xong'
        },
        DISCORD: {
            webhookUrl: 'https://discord.com/api/webhooks/1374401953374666864/sXgxVbDOPQDBK29JFfNqmBRs_K8ZRSxY5t-EQ9W7TAbzx6QWJKWmyp0ukbGVmMYwfqc6' // Thay thế bằng webhook của bạn
        },
        MIN_VALUE: 0
    };

    let lastAttackTime = 0;
    const attackCooldown = 5 * 60 * 1000; // 5 phút dưới dạng milliseconds
    let isAttackInProgress = false;

    let lastSentHash = ''; // Lưu trạng thái lần gửi cuối
    let isRunning = false; // Cờ kiểm soát quá trình chạy
    const BLACK_LIST = []
    const baseUrl= "https://cmangax2.com"

    const getCharacterId = () => {
        const scripts = document.getElementsByTagName('script');
        for (const script of scripts) {
            // Regex cải tiến: Bắt cả trường hợp có khoảng trắng và dấu nháy
            const match = script.textContent.match(/my_character\s*=\s*['"]?(\d+)['"]?/);
            if (match) return parseInt(match[1], 10);
        }
        console.error('Không tìm thấy my_character trong script');
        return null;
    };
    const getMineEnergy = async () => {
        try {
            const characterId = getCharacterId();
            if (!characterId) {
                console.error('Không tìm thấy character ID');
                return 0;
            }

            const response = await fetch(
                `${baseUrl}/api/character_energy_mine?character=${characterId}`
            );
            const data = await response.json();
            return data.current || 0;
        } catch (e) {
            console.error('Lỗi khi check lượt đánh:', e);
            return 0;
        }
    };


    const calculateValue = (reward) => {
        let total = 0;
        const validItems = {};

        for (const itemKey in reward) {
            if (priorityItem[itemKey]) {
                const amount = reward[itemKey].amount || 0;
                const price = priceInMarket[itemKey] || 0;

                if (amount > 0 && price > 0) {
                    const value = amount * price;
                    total += value;
                    const itemName = priorityItem[itemKey]
                    validItems[itemName] = (validItems[itemName] || 0) + amount;
                }
            }
        }

        return { total, validItems };
    };

    const processMiner = async (miner, area, index) => {
        try {
            const data = JSON.parse(miner.data);
            const reward = data.miner?.reward;

            //const isProtect = data.miner?.protect === true;
            const isProtect = !!data.miner?.protect;

            if (!reward || typeof reward !== 'object' || isProtect) return null;

            const { total, validItems } = calculateValue(reward);
            if (total <= 0) return null;

            return {
                area: area,
                rare: data.rare,
                stt: index + 1,
                mine_id: parseInt(miner.id_score, 10),
                character_id: parseInt(miner.target, 10),
                author: data.miner?.info?.name,
                total_value: total,
                valid_items: validItems,
                isProtect:isProtect
            };
        } catch (e) {
            console.error(`Error processing miner: ${e}`);
            return null;
        }
    };



    const processArea = async (area) => {
        try {
            const response = await fetch(
                `https://cmangax2.com/api/score_list?type=battle_mine&area=${area}`
            );
            const miners = await response.json();

            const minersPromises = miners.map((miner, index) =>processMiner(miner, area, index));

            const areaResults = await Promise.all(minersPromises);
            return areaResults.filter(item => item !== null);
        } catch (e) {
            console.error(`Error processing area ${area}: ${e}`);
            GM_notification(`Lỗi khi xử lý tầng ${area}: ${e.message}`, 'Lỗi');
            return [];
        }
    };

    const processBattle = async (mine_id, target) => {
        try {
            console.log("mine_id", mine_id);
            console.log(target)
            const response = await fetch(
                `${baseUrl}/assets/ajax/character_activity.php`,
                {
                    method: 'POST',
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded"
                    },
                    body: `action=battle_mine_challenge&mine_id=${mine_id}&target=${target}`
                }
            );

            const responseText = await response.text();

            const parser = new DOMParser();
            const doc = parser.parseFromString(responseText, "text/html");
            const scripts = doc.getElementsByTagName('script');

            let popupData = null;
            let isSuccess = false;
            let errorMessage = '';

            for (const script of scripts) {
                const scriptContent = script.textContent;

                if (scriptContent.includes("alertify.success('Khiêu chiến thành công')")) {
                    isSuccess = true;
                    console.log('[DEBUG] Detected success message');
                }


                if (scriptContent.startsWith('popup_data =')) {
                    const dataString = scriptContent
                    .replace('popup_data =', '')
                    .replace(/;[\s\S]*$/, '');

                    console.log('[DEBUG] Raw popup_data string:', dataString);

                    try {
                        const rawData = JSON.parse(dataString.trim());
                        console.log('[DEBUG] Parsed popup_data:', rawData);
                        popupData = Object.fromEntries(
                            Object.entries(rawData)
                            .filter(([key]) => key !== 'gold' && key !== 'mine_ore' )
                            .map(([key, value]) => {
                                if (value && typeof value === 'object' && 'amount' in value) {
                                    return [key, value.amount];
                                }
                                return [key, value];
                            })
                        );
                    } catch (e) {
                        console.error('[DEBUG] Lỗi parse popup_data:', e);
                    }
                }
            }


            return {
                success: isSuccess,
                popupData: popupData,
                error: errorMessage,
            };

        } catch (error) {
            console.error('[DEBUG] Lỗi processBattle:', {
                error: error,
                stack: error.stack
            });
            return {
                success: false,
                error: error.message,
                rawResponse: null
            };
        }
    };

    const getHmkLevel = async () => {
        const areas = Array.from({ length: 11 }, (_, i) => i + 1);

        const areaPromises = areas.map(area => processArea(area));
        const allResults = await Promise.all(areaPromises);
        return allResults
            .flatMap(arr => arr)
            .sort((a, b) => b.total_value - a.total_value);
    };

    const sendToTelegram = async (message) => {
        const token = '8178445381:AAEL5AHPsPcsLYZa5qQvnWPI-3EQI3gMj04';
        const chatId = '5709122878';

        return new Promise((resolve) => {
            GM.xmlHttpRequest({
                method: 'POST',
                url: `https://api.telegram.org/bot${token}/sendMessage`,
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify({
                    chat_id: chatId,
                    text: message,
                    parse_mode: 'HTML'
                }),
                onload: (response) => {
                    if (response.status !== 200) {
                        console.error('Lỗi Telegram:', response.responseText);
                    }
                    resolve();
                },
                onerror: (error) => {
                    console.error('Lỗi kết nối Telegram:', error);
                    resolve();
                }
            });
        });
    };
    const sendToDiscord = async (message) => {
        return new Promise((resolve) => {
            GM.xmlHttpRequest({
                method: 'POST',
                url: NOTIFICATION_CONFIG.DISCORD.webhookUrl,
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify({
                    content: '📢 Thông báo khai thác mới',
                    embeds: [{
                        title: 'Chi tiết kết quả',
                        description: message,
                        color: 0x00ff00,
                        timestamp: new Date().toISOString()
                    }]
                }),
                onload: (response) => {
                    if (response.status < 200 || response.status >= 300) {
                        console.error('Lỗi Discord:', response.responseText);
                    }
                    resolve();
                },
                onerror: (error) => {
                    console.error('Lỗi kết nối Discord:', error);
                    resolve();
                }
            });
        });
    };

    const formatForDiscord = (filtered) => {
        const now = new Date();
        const options = {
            timeZone: 'Asia/Ho_Chi_Minh',
            hour12: false,
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit'
        };

        const vnTime = new Intl.DateTimeFormat('vi-VN', options)
        .format(now)
        .replace(/(\d+)\/(\d+)\/(\d+),/, '$1-$2-$3');

        let message = `📊 **KẾT QUẢ KHAI THÁC MỚI LÚC ${vnTime}**\n\n`;

        filtered.slice(0, 15).forEach((item, index) => {
            const rare = parseInt(item.rare) || 0;
            const rareInfo = RARE_COLORS[rare] || RARE_COLORS[0];

            message += `🏷 **#${index + 1}**\n`
                + `├ Tầng: ${item.area}\n`
                + `├ Rare: ${rareInfo.name}\n`
                + `├ Vị trí: ${item.stt}\n`
                + `├ Mine id: ${item.mine_id}\n`
                + `├ Tác giả: ${item.author || 'Ẩn danh'} (ID: ${item.character_id})\n`
                + `├ Giá trị: ${item.total_value.toFixed(2)}💰\n`
                + `└ Vật phẩm:\n${Object.entries(item.valid_items)
                                  .map(([name, amount]) => `   ↪ ${name} x${amount}`)
                                  .join('\n')}\n\n`;
        });

        //console.log("message to discord", message)
        return message;
    };

    const formatResults = (filtered) => {
        const now = new Date();
        const options = {
            timeZone: 'Asia/Ho_Chi_Minh',
            hour12: false,
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit'
        };

        const vnTime = new Intl.DateTimeFormat('vi-VN', options)
        .format(now)
        .replace(/(\d+)\/(\d+)\/(\d+),/, '$1-$2-$3');
        let message = `📊 <b>KẾT QUẢ KHAI THÁC MỚI LÚC ${vnTime}</b>\n\n`;
        filtered.slice(0, 15).forEach((item, index) => {
            const rare = parseInt(item.rare) || 0;
            const rareInfo = RARE_COLORS[rare] || RARE_COLORS[0];
            message += `🏷 <b>#${index + 1}</b>\n`
                     + `┣ Tầng: ${item.area}\n`
                     + `┣ Rare: ${rareInfo.name}\n`
                     + `┣ Vị ttrí ${item.stt}\n`
                     + `├ Mine id: ${item.mine_id}\n`
                     + `┣ Tác giả: ${item.author || 'Ẩn danh'} id: ${item.character_id}\n`
                     + `┣ Giá trị: ${item.total_value.toFixed(2)}💰\n`
                     + `┗ Vật phẩm: ${Object.entries(item.valid_items)
                                   .map(([name, amount]) => `${amount}x${name}`)
                                   .join('\n')}\n\n`;
         });

        //console.log("message for tele", message)
        return message;
    };

   const formatAttackResult = (mine, result, remainingAttacks) => {
       const now = new Date();
       const status = result.success ? '✅ THÀNH CÔNG' : '❌ THẤT BẠI';

       let message = `⚔️ **KẾT QUẢ TẤN CÔNG MỎ ${mine.mine_id}** (${status})\n`
       + `⏰ Thời gian: ${now.toLocaleString('vi-VN')}\n`
       + `🏷 Tầng: ${mine.area} | Rare: ${RARE_COLORS[mine.rare].name}\n`
       + `🎯 Mục tiêu: ${mine.author || 'Ẩn danh'} (ID: ${mine.character_id})\n`;

       if (result.success) {
           const popupData = result.popupData || {};
           const validItems = result.valid_items || {};

           // 📥 Nhận được
           message += "\n📥 **Nhận được:**\n";
           let hasGain = false;

           for (const [key, value] of Object.entries(validItems)) {
               const itemName = priorityItem[key] || key;
               message += `- ${itemName}: ${value}\n`;
               hasGain = true;
           }
           if (!hasGain) message += "- Không có vật phẩm nào\n";

           // 📤 Mất đi
           message += "\n📤 **Mất đi:**\n";
           let hasLoss = false;

           if (mine.valid_items) {
               mine.valid_items.forEach(originalItem => {
                   const itemName = Object.keys(originalItem)[0];
                   const originalAmount = Object.values(originalItem)[0];
                   const gainAmount = popupData[itemName] || 0;
                   const lostAmount = originalAmount - gainAmount;

                   if (lostAmount > 0) {
                       const displayName = priorityItem[itemName] || itemName;
                       message += `- ${displayName}: ${lostAmount}\n`;
                       hasLoss = true;
                   }
               });
           }

           if (!hasLoss) message += "- Không mất vật phẩm\n";
       } else {
           message += `\n📛 Lý do: ${result.error || 'Do bạn yếu'}\n`;
       }
      message+= `\n Lượt đánh còn lại: ${remainingAttacks}\n`
       return message;
   };


    const checkAndNotify = async () => {
        if (isRunning || isAttackInProgress) return;
        isRunning = true;

        try {
            const results = await getHmkLevel();
            const filtered = results.filter(item =>
                                            item.total_value >= NOTIFICATION_CONFIG.MIN_VALUE &&
                                            item.isProtect === false &&
                                            !BLACK_LIST.includes(item.character_id)
                                           );

            // Thêm thông tin lượt đánh vào log
            const remainingAttacks = await getMineEnergy();
            console.log(`Lượt đánh còn lại: ${remainingAttacks}`);

            // Gửi thông tin các mỏ trước
            const currentHash = JSON.stringify(filtered);
            if (filtered.length > 0 && currentHash !== lastSentHash) {
                lastSentHash = currentHash;
                const messageAttack ='';

                if (remainingAttacks > 0) {
                    isAttackInProgress = true;
                    const targetMine = filtered[0];
                    const battleResult = await processBattle(targetMine.mine_id, 'private');
                    console.log(`Kết quả tấn công: ${battleResult.success ? 'Thành công' : 'Thất bại'}`);
                    const remainingAttacks = await getMineEnergy()
                    messageAttack = formatAttackResult(targetMine, battleResult, remainingAttacks);
                    GM_notification(
                        `Tấn công mỏ ${battleResult.success ? 'thành công' : 'thất bại'}!`,
                        'Kết quả tấn công'
                    );
                    isAttackInProgress = false;
                }

                const telegramMessage = formatResults(filtered);
                const discordMessage = formatForDiscord(filtered);
                await Promise.all([
                    //sendToTelegram(telegramMessage),
                    sendToDiscord(discordMessage),
                    sendToDiscord(messageAttack)
                ]);
            }

        } catch (e) {
            GM_notification(`Lỗi hệ thống: ${e.message}`, 'Lỗi');
            isAttackInProgress = false;
        } finally {
            isRunning = false;
        }
    };


    //Kiểm tra định kỳ
    setTimeout(function runner() {
        checkAndNotify();
        setTimeout(runner, 30000);
    }, 1000);

})();