import json, os, time, sys, random, re, unicodedata
from datetime import datetime, timedelta
from queue import Queue
import threading
import logging
import urllib.parse
import queue
import math
from PIL import Image
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
import ffmpeg
import pyfiglet
from colorama import Fore, Style, init
from zlapi import ZaloAPI
from zlapi.models import *
from config import API_KEY, SECRET_KEY, IMEI, SESSION_COOKIES, ADMIN, PREFIX
from xkprj import CommandHandler
from logging_utils import Logging
from event.event import handleGroupEvent
from threading import Lock
from commands.antispam import AntiSpamHandler
from commands.antilink import AntiLinkHandler
from commands.loctk import LocTKHandler
from commands.antiundo import UndoHandler
from commands.lockbot import LockBotHandler
init(autoreset=True)
COLORS = [Fore.RED, Fore.YELLOW, Fore.GREEN, Fore.CYAN, Fore.BLUE, Fore.MAGENTA, Fore.WHITE]

def banner():
    text = """
╔══════════════════════════════════════╗
║ 🚀 Bot đã khởi động thành công! 🎉          
╠══════════════════════════════════════╣
║ 👑  Developer: Vũ Xuân Kiên
║ 🆔  Facebook : https://fb.me/vuxuankiendzvcl
║ 📞  Zalo     : 0345864723
║ 🌐  GitHub   : https://github.com/xuankiendev
╚══════════════════════════════════════╝

╔══════════════════════════════════════╗
║ 🚀 📌 Đăng nhập thành công! 🎉          
╚══════════════════════════════════════╝
"""
    for i, char in enumerate(text):
        color = COLORS[i % len(COLORS)]
        print(color + char, end='')
banner()

class ResetBot:
    def __init__(self, reset_interval=900):
        self.reset_event = threading.Event()
        self.reset_interval = reset_interval
        self.load_autorestart_setting()

    def load_autorestart_setting(self):
        try:
            with open("seting.json", "r") as f:
                settings = json.load(f)
                self.autorestart = settings.get("autorestart", "False") == "True"
            if self.autorestart:
                logger.restart("Chế độ auto restart đang được bật")
                threading.Thread(target=self.reset_code_periodically, daemon=True).start()
            else:
                logger.restart("Chế độ auto restart đang được tắt")
        except Exception as e:
            logger.error(f"Lỗi khi tải cấu hình autorestart: {e}")
            self.autorestart = False

    def reset_code_periodically(self):
        while not self.reset_event.is_set():
            time.sleep(self.reset_interval)
            logger.restart("Đang tiến hành khởi động lại bot...")
            self.restart_bot()

    def restart_bot(self):
        try:
            current_time = datetime.now().strftime("%H:%M:%S")
            gui_message = f"Bot khởi động lại thành công vào lúc: {current_time}"
            logger.restart(gui_message)
            python = sys.executable
            os.execl(python, python, *sys.argv)
        except Exception as e:
            logger.error(f"Lỗi khi khởi động lại bot: {e}")

logger = Logging()

class Client(ZaloAPI):
    def __init__(self, api_key, secret_key, imei, session_cookies, reset_interval=900, auto_approve_interval=0):
        super().__init__(api_key, secret_key, imei=imei, session_cookies=session_cookies)
        self.command_handler = CommandHandler(self)
        self.reset_bot = ResetBot(reset_interval)
        self.session = requests.Session()
        retry_strategy = Retry(total=5, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504])
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)
        self.message_queue = Queue()
        self.processed_messages = set()
        self.response_time_limit = timedelta(seconds=1)
        self.ADMIN = ADMIN
        self.group_info_cache = {}
        self.last_sms_times = {}
        self.temp_thread_storage = {}
        self.search_results = {}
        self.search_result_messages = {}
        self.search_result_ttl = 24
        self.spam_enabled = self.load_spam_settings()
        self.user_message_times = {}
        self.warned_users = {}
        self.spam_threshold = 5
        self.kick_threshold = 6
        self.spam_window = 7
        self.loctk_enabled = self.load_loctk_settings()
        self.banned_words = self.load_banned_words()
        self.banned_word_violations = {}
        self.FileNM = 'database/dataundo.json'
        self.undo_enabled = self.load_undo_settings()
        self.locked_users_file = 'data/locked_users.json'
        self.locked_users = self.load_locked_users()
        self.last_undo_reset = self.load_last_undo_reset()
        self.antilink_enabled = self.load_antilink_settings()
        self.antilink_violations = self.load_antilink_violations()  # Thêm lưu vi phạm anti-link
        self.check_and_reset_undo()
        self.antispam_handler = AntiSpamHandler(self)
        self.loctk_handler = LocTKHandler(self)
        self.undo_handler = UndoHandler(self)
        self.lockbot_handler = LockBotHandler(self)
        self.antilink_handler = AntiLinkHandler(self)
        self.auto_approve_enabled = self.load_auto_approve_settings()
        self.auto_approve_interval = auto_approve_interval
        self.last_auto_approve_check = datetime.min
        self.group_message_counts = {}
        self.settings = self.load_settings()
        self.duyetbox_data = self.load_duyetbox_data()
        self.ADMIN = self.settings.get("admin")
        self.ADM = self.settings.get("adm", [])
        self.sendtask_autosticker_file = "modules/cache/sendtask_autosticker.json"
        self.datasticker_file = "database/datasticker.json"
        threading.Thread(target=self.cleanup_expired_search_results, daemon=True).start()
        threading.Thread(target=self.auto_approve_periodically, daemon=True).start()
        logger.logger('EVENT GROUP', 'Tiến hành nhận sự kiện các nhóm...')
        self.load_sticker_data()

    def load_sticker_data(self):
        try:
            with open(self.datasticker_file, "r") as f:
                self.sticker_data = json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            self.sticker_data = []
            self.save_sticker_data()

    def save_sticker_data(self):
        with open(self.datasticker_file, "w") as f:
            json.dump(self.sticker_data, f, indent=4)

    def load_allowed_groups(self):
        try:
            with open(self.sendtask_autosticker_file, "r") as f:
                return json.load(f).get("groups", [])
        except (FileNotFoundError, json.JSONDecodeError):
            return []

    def save_allowed_groups(self, allowed_groups):
        with open(self.sendtask_autosticker_file, "w") as f:
            json.dump({"groups": allowed_groups}, f, indent=4)

    def auto_approve_periodically(self):
        while True:
            self.check_and_handle_auto_approve()
            time.sleep(self.auto_approve_interval)

    def onEvent(self, event_data, event_type):
        handleGroupEvent(self, event_data, event_type)

    def send_request(self, url):
        try:
            response = self.session.get(url)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            logger.error(f"Lỗi request: {e}")
            return None

    def load_auto_approve_settings(self):
        try:
            with open("data/auto_approve_settings.json", "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return {}
        except json.JSONDecodeError:
            logger.error("Lỗi khi đọc file auto_approve_settings.json. Khởi tạo settings trống.")
            return {}

    def save_auto_approve_settings(self):
        try:
            with open("data/auto_approve_settings.json", "w") as f:
                json.dump(self.auto_approve_enabled, f, indent=4)
        except Exception as e:
            logger.error(f"Lỗi khi lưu cài đặt auto-approve: {e}")

    def handle_auto_approve_command(self, message_text, message_object, thread_id, thread_type, author_id):
        if author_id not in self.ADMIN:
            self.replyMessage(Message(text="Bạn không có quyền sử dụng lệnh này."), message_object, thread_id, thread_type)
            return

        parts = message_text.split()
        if len(parts) < 2:
            self.replyMessage(Message(text="Cách dùng: .autoapprv <on/off>"), message_object, thread_id, thread_type)
            return

        action = parts[1].lower()
        if action == "on":
            self.auto_approve_enabled[thread_id] = True
            self.save_auto_approve_settings()
            self.replyMessage(Message(text="Đã bật tự động duyệt thành viên mới."), message_object, thread_id, thread_type)
        elif action == "off":
            self.auto_approve_enabled[thread_id] = False
            self.save_auto_approve_settings()
            self.replyMessage(Message(text="Đã tắt tự động duyệt thành viên mới."), message_object, thread_id, thread_type)
        else:
            self.replyMessage(Message(text="Lệnh không hợp lệ. Sử dụng 'on' hoặc 'off'."), message_object, thread_id, thread_type)

    def load_spam_settings(self):
        try:
            with open("data/spam_settings.json", "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return {}
        except json.JSONDecodeError:
            logger.error("Lỗi khi đọc file spam_settings.json. Khởi tạo settings trống.")
            return {}

    def save_spam_settings(self):
        try:
            with open("data/spam_settings.json", "w") as f:
                json.dump(self.spam_enabled, f, indent=4)
        except Exception as e:
            logger.error(f"Lỗi khi lưu cài đặt anti-spam: {e}")

    def load_loctk_settings(self):
        try:
            with open("data/loctk_settings.json", "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return {}
        except json.JSONDecodeError:
            logger.error("Lỗi khi đọc file loctk_settings.json. Khởi tạo settings trống.")
            return {}

    def save_loctk_settings(self):
        try:
            with open("data/loctk_settings.json", "w") as f:
                json.dump(self.loctk_enabled, f, indent=4)
        except Exception as e:
            logger.error(f"Lỗi khi lưu cài đặt lọc từ: {e}")

    def load_banned_words(self):
        try:
            with open("data/banned_words.json", "r", encoding='utf-8') as f:
                data = json.load(f)
                return data.get("banned_words", {"words": []}).get("words", [])
        except FileNotFoundError:
            logger.error("File banned_words.json không tìm thấy.")
            return []
        except json.JSONDecodeError:
            logger.error("Lỗi khi đọc file banned_words.json.")
            return []

    def save_banned_words(self):
        try:
            with open("data/banned_words.json", "w", encoding='utf-8') as f:
                json.dump({"banned_words": {"words": self.banned_words}}, f, indent=4, ensure_ascii=False)
        except Exception as e:
            logger.error(f"Lỗi khi lưu danh sách từ cấm: {e}")

    def load_undo_settings(self):
        try:
            with open("data/undo_settings.json", "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return {"groups": {}}
        except json.JSONDecodeError:
            logger.error("Lỗi khi đọc file undo_settings.json. Khởi tạo settings mặc định.")
            return {"groups": {}}

    def save_undo_settings(self):
        try:
            with open("data/undo_settings.json", "w") as f:
                json.dump(self.undo_enabled, f, indent=4)
        except Exception as e:
            logger.error(f"Lỗi khi lưu cài đặt undo: {e}")

    def Luuvideo(self, cliMsgId):
        with open(self.FileNM, 'r') as f:
            messages = json.load(f)
        cliMsgId = str(cliMsgId)
        for message in messages:
            if message['cliMsgId'] == cliMsgId:
                return message['content']
        return None

    def FileUndo(self):
        try:
            if not os.path.exists(self.FileNM):
                with open(self.FileNM, 'w') as f:
                    json.dump([], f)
            else:
                with open(self.FileNM, 'r') as f:
                    json.load(f)
        except (json.JSONDecodeError, OSError, IOError):
            with open(self.FileNM, 'w') as f:
                json.dump([], f)

    def message_object_undo(self, message_object, message_text):
        content = message_object.content if isinstance(message_object.content, dict) else {}
        if isinstance(content, dict) and 'href' in content:
            stored_content = {
                'href': content.get('href'),
                'thumb': content.get('thumb'),
                'params': content.get('params', {})
            }
        else:
            stored_content = content
        return {
            'msgId': message_object.msgId,
            'uidFrom': message_object.uidFrom,
            'cliMsgId': message_object.cliMsgId,
            'msgType': message_object.msgType,
            'content': stored_content,
            'text': message_text,
            'params': message_object.params,
            'id': content.get('id', ''),
            'catId': content.get('catId', '')
        }

    def LuuNoiDungThuHoi(self, message_object, message_text):
        noidung_1 = self.message_object_undo(message_object, message_text)
        with open(self.FileNM, 'r') as f:
            messages = json.load(f)
        messages.append(noidung_1)
        with open(self.FileNM, 'w') as f:
            json.dump(messages, f, indent=4, ensure_ascii=False)

    def load_last_undo_reset(self):
        try:
            with open("data/undo_reset.json", "r") as f:
                data = json.load(f)
                return datetime.fromisoformat(data.get('last_reset', datetime.min.isoformat()))
        except (FileNotFoundError, json.JSONDecodeError):
            return datetime.min

    def save_last_undo_reset(self, last_reset_time):
        try:
            with open("data/undo_reset.json", "w") as f:
                json.dump({"last_reset": last_reset_time.isoformat()}, f, indent=4)
        except Exception as e:
            logger.error(f"Lỗi khi lưu thời gian reset undo: {e}")

    def check_and_reset_undo(self):
        now = datetime.now()
        if (now - self.last_undo_reset) >= timedelta(days=1):
            self.reset_undo_data()
            self.last_undo_reset = now
            self.save_last_undo_reset(now)

    def reset_undo_data(self):
        try:
            with open(self.FileNM, 'w') as f:
                json.dump([], f)
            logger.info("Đã xóa nội dung file undo.json.")
        except Exception as e:
            logger.error(f"Lỗi khi xóa nội dung file undo.json: {e}")

    def TimTinNhanThuHoi(self, cliMsgId):
        with open(self.FileNM, 'r') as f:
            messages = json.load(f)
        cliMsgId = str(cliMsgId)
        for message in messages:
            if message['cliMsgId'] == cliMsgId:
                return message['id'], message['catId']
        return None, None

    def Anhthuhoi(self, cliMsgId):
        with open(self.FileNM, 'r') as f:
            messages = json.load(f)
        cliMsgId = str(cliMsgId)
        for message in messages:
            if message['cliMsgId'] == cliMsgId:
                return message['content']
        return None

    def DownAnhThuHoi(self, media_url, anh='undo.jpg'):
        try:
            response = requests.get(media_url)
            response.raise_for_status()
            with open(anh, 'wb') as f:
                f.write(response.content)
        except requests.exceptions.RequestException as e:
            logger.error(f"Lỗi khi tải ảnh thu hồi: {e}")
            return None
        return anh

    def send_sticker(self, id, catId, thread_id, thread_type, author_id, message_object, send_time):
        try:
            author_name = self.fetchUserInfo(author_id).name
            text = f'''@{author_name}\nĐã thu hồi sticker\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nSticker sẽ được gửi bên dưới'''
            offset = text.find(f"@{author_name}")
            self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
            self.sendSticker(1, id, catId, thread_id, thread_type)
        except Exception as e:
            logger.error(f"Lỗi khi gửi sticker: {e}")

    def handle_lockbot_command(self, message_text, message_object, thread_id, thread_type, author_id):
        self.lockbot_handler.handle_lockbot_command(message_text, message_object, thread_id, thread_type, author_id)

    def handle_unlockbot_command(self, message_text, message_object, thread_id, thread_type, author_id):
        self.lockbot_handler.handle_unlockbot_command(message_text, message_object, thread_id, thread_type, author_id)

    def load_mute_list(self):
        mute_vxk = "data/khoamom.json"
        if os.path.exists(mute_vxk):
            with open(mute_vxk, 'r') as f:
                return json.load(f)
        return {}

    def is_user_muted(self, thread_id, author_id):
        mute_list = self.load_mute_list()
        if thread_id in mute_list:
            return str(author_id) in mute_list[thread_id]
        return False

    def load_antilink_settings(self):
        try:
            with open("data/antilink_settings.json", "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return {}
        except json.JSONDecodeError:
            logger.error("Lỗi khi đọc file antilink_settings.json. Khởi tạo settings trống.")
            return {}

    def save_antilink_settings(self):
        try:
            with open("data/antilink_settings.json", "w") as f:
                json.dump(self.antilink_enabled, f, indent=4)
        except Exception as e:
            logger.error(f"Lỗi khi lưu cài đặt antilink: {e}")

    def load_antilink_violations(self):
        try:
            if not os.path.exists("data/antilink_violations.json"):
                with open("data/antilink_violations.json", "w") as f:
                    json.dump({}, f)
            with open("data/antilink_violations.json", "r") as f:
                return json.load(f)
        except json.JSONDecodeError as e:
            logger.error(f"Lỗi load antilink_violations.json: {e}")
            return {}
        except Exception as e:
            logger.error(f"Lỗi khi tải vi phạm anti-link: {e}")
            return {}

    def save_antilink_violations(self):
        try:
            with open("data/antilink_violations.json", "w") as f:
                json.dump(self.antilink_violations, f, indent=4)
        except Exception as e:
            logger.error(f"Lỗi khi lưu vi phạm anti-link: {e}")

    def hex_to_ansi(self, hex_color):
        hex_color = hex_color.lstrip('#')
        r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
        return f'\033[38;2;{r};{g};{b}m'

    def is_group_admin(self, thread_id, user_id):
        try:
            if thread_id in self.group_info_cache:
                group_data = self.group_info_cache[thread_id]
            else:
                group_info = self.fetchGroupInfo(thread_id)
                if not group_info or not hasattr(group_info, 'gridInfoMap') or thread_id not in group_info.gridInfoMap:
                    logger.error(f"Không tìm thấy thông tin nhóm hoặc gridInfoMap cho thread_id: {thread_id}")
                    return False
                group_data = group_info.gridInfoMap[thread_id]
                self.group_info_cache[thread_id] = group_data
            creator_id = group_data.get('creatorId')
            admin_ids = group_data.get('adminIds', [])
            return user_id == creator_id or user_id in admin_ids
        except Exception as e:
            logger.error(f"Lỗi khi kiểm tra admin nhóm: {e}")
            return False

    def get_content_message(self, message_object):
        if message_object.msgType == 'chat.sticker':
            return ""

        content = message_object.content

        if isinstance(content, dict) and 'title' in content:
            text_to_check = content['title']
        else:
            text_to_check = content if isinstance(content, str) else ""
        return text_to_check

    def is_url_in_message(self, message_object):
        if message_object.msgType == 'chat.sticker':
            return False
        
        text_to_check = self.get_content_message(message_object)

        url_regex = re.compile(
            r'http[s]?://'
            r'(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
        )
        
        if re.search(url_regex, text_to_check):
            return True
        
        return False
    
    def get_allow_link_status(self, thread_id):
        return self.antilink_enabled.get(thread_id, False)

    def check_and_handle_auto_approve(self):
        now = datetime.now()
        if (now - self.last_auto_approve_check).total_seconds() >= self.auto_approve_interval:
            for thread_id, enabled in self.auto_approve_enabled.items():
                if enabled == True:
                    self.auto_approve_pending_members(thread_id)
            self.last_auto_approve_check = now

    def auto_approve_pending_members(self, thread_id):
        try:
            group_info = self.fetchGroupInfo(thread_id)
            if not group_info or not hasattr(group_info, 'gridInfoMap'):
                return False
            group_data = group_info.get('gridInfoMap', {}).get(thread_id)
            if not isinstance(group_data, dict):
              return False
            pending_members = group_data.get('pendingApprove', {}).get('uids', [])
            for member_id in pending_members:
                if hasattr(self, 'handleGroupPending'):
                    if thread_id in self.auto_approve_enabled:
                        self.handleGroupPending(member_id, thread_id)
            return True
        except Exception as e:
            return False

    def handle_auto_sticker(self, thread_id, thread_type, message_object):
        allowed_groups = self.load_allowed_groups()  
        if thread_type == ThreadType.GROUP and thread_id in allowed_groups:
            if message_object.msgType == "webchat":
                self.group_message_counts[thread_id] = self.group_message_counts.get(thread_id, 0) + 1
                if self.group_message_counts[thread_id] >= random.randint(10, 15):
                    if self.sticker_data:
                        sticker = random.choice(self.sticker_data)
                        self.sendSticker(1, sticker["id"], sticker["catId"], thread_id, thread_type)
                        self.group_message_counts[thread_id] = 0

    def load_settings(self):
        try:
            with open("seting.json", "r", encoding="utf-8") as f:
                return json.load(f)
        except FileNotFoundError:
            logger.error("Không tìm thấy file setting.json")
            return {}
        except json.JSONDecodeError:
            logger.error("Lỗi giải mã JSON trong file setting.json")
            return {}

    def load_duyetbox_data(self):
        try:
            with open("modules/cache/duyetboxdata.json", "r", encoding="utf-8") as f:
                return json.load(f)
        except FileNotFoundError:
            logger.warning("Không tìm thấy file duyetboxdata.json")
            return []
        except json.JSONDecodeError:
            logger.error("Lỗi giải mã JSON trong file duyetboxdata.json")
            return []

    def is_allowed_author(self, author_id):
        return str(author_id) in self.ADM or str(author_id) == str(self.ADMIN)

    def is_duyetbox_thread(self, thread_id):
        return str(thread_id) in self.duyetbox_data

    def onMessage(self, mid, author_id, message, message_object, thread_id, thread_type):
        try:
            message_text = message.text if isinstance(message, Message) else str(message)
            author_name = self.fetchUserInfo(author_id).changed_profiles.get(author_id, {}).get('zaloName', 'đéo xác định')
            logger.info(f"{message}")

            if self.is_user_muted(thread_id, author_id):
                self.deleteGroupMsg(message_object.msgId, author_id, message_object.cliMsgId, thread_id)
                return

            if author_id in self.locked_users:
                return

            # Kiểm tra và xử lý anti-link
            if self.get_allow_link_status(thread_id) and self.is_url_in_message(message_object) and author_id not in ADMIN:
                if not self.is_group_admin(thread_id, author_id):
                    self.deleteGroupMsg(message_object.msgId, author_id, message_object.cliMsgId, thread_id)
                    # Lưu và đếm vi phạm
                    if thread_id not in self.antilink_violations:
                        self.antilink_violations[thread_id] = {}
                    self.antilink_violations[thread_id][author_id] = self.antilink_violations[thread_id].get(author_id, 0) + 1
                    self.save_antilink_violations()
                    violation_count = self.antilink_violations[thread_id][author_id]
                    group_msg = f"Đại Ka cấm gửi link trong nhóm. @{author_name} đã vi phạm {violation_count}/3 lần."
                    private_msg = f"Đại Ka cấm gửi link trong nhóm. Bạn đã vi phạm {violation_count}/3 lần."
                    offset = group_msg.find(f"@{author_name}")
                    self.replyMessage(Message(text=group_msg, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=10000)
                    self.replyMessage(Message(text=private_msg), message_object, author_id, ThreadType.USER, ttl=10000)
                    if violation_count >= 3:
                        self.blockUsersInGroup(author_id, thread_id)
                        kick_msg = f"@{author_name} đã bị kick vì vi phạm liên kết quá 3 lần."
                        offset = kick_msg.find(f"@{author_name}")
                        self.replyMessage(Message(text=kick_msg, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=10000)
                        del self.antilink_violations[thread_id][author_id]  # Xóa ID sau khi kick
                        if not self.antilink_violations[thread_id]:
                            del self.antilink_violations[thread_id]
                        self.save_antilink_violations()
                    return

            if message_object.msgType == 'chat.sticker':
                allowed_groups = self.load_allowed_groups()
                if thread_type == ThreadType.GROUP and thread_id in allowed_groups:
                    sticker_id = message_object.content.id
                    sticker_catId = message_object.content.catId
                    logger.info(f"Sticker ID: {sticker_id}, Sticker CatId: {sticker_catId}")
                    sticker_info = {"id": sticker_id, "catId": sticker_catId}
                    if sticker_info not in self.sticker_data:
                        self.sticker_data.append(sticker_info)
                        self.save_sticker_data()

            command_handlers = {
                ".antispam": self.antispam_handler.handle_antispam_command,
                ".loctk": self.loctk_handler.handle_loctk_command,
                ".antiundo": self.undo_handler.handle_undo_command,
                ".lockbot": self.lockbot_handler.handle_lockbot_command,
                ".unlockbot": self.lockbot_handler.handle_unlockbot_command,
                ".listlb": self.lockbot_handler.handle_listlockbot_command,
                ".antilink": self.antilink_handler.handle_antilink_command,
                ".autoapprv": self.handle_auto_approve_command
            }

            for prefix, handler in command_handlers.items():
                if message_text.lower().startswith(prefix):
                    handler(message_text, message_object, thread_id, thread_type, author_id)
                    return

            self.command_handler.handle_command(message_text, author_id, message_object, thread_id, thread_type)
            self.handle_auto_sticker(thread_id, thread_type, message_object)

        except Exception as e:
            logger.error(f"Lỗi trong onMessage: {e}")
            if message and thread_type == ThreadType.USER:
                if author_id == self.uid:
                    return
                now = time.time()
                if author_id in self.temp_thread_storage:
                    last_message_time = self.temp_thread_storage[author_id]
                    if now - last_message_time < 360000:
                        return
                self.temp_thread_storage[author_id] = now
                msg = '''
~ Cung Cấp Source Chất Lượng 

⓵ Source VXK Zalo Bot
💳 : 448.000 VNĐ | Có Tiền Thì Làm Phiền Thiên Hạ
> Đôi Lời - Project Này Đầy Đủ Chức Năng, Game Giải Trí, Quản Lý Nhóm, Commands (90+) /-ok
• Được Support Tận Giường, Sẽ Không Update Nữa Nên Giá Vẫn Giữ

🔗 Group Zalo Dev&Test Bot: https://zalo.me/g/dgbqdl384

> Ưng Thì Liên Hệ Ngay:

• Zalo: 034.586.4723
• Facebook: /vuxuankiendzvcl
• Telegram: @xkprj or t.me/xkprj
'''
                self.replyMessage(Message(text=msg), message_object, thread_id, thread_type, ttl=300000)
                self.sendBusinessCard(userId=790318026347075757, qrCodeUrl=self.fetchUserInfo(self.uid).changed_profiles[self.uid]['avatarUrl'], phone="VXK_ZaloBot!", thread_id=thread_id, thread_type=thread_type, ttl=300000)

        self.check_spam(author_id, message_object, thread_id, thread_type)
        if str(author_id) not in ADMIN:
            self.check_banned_words(message_text, message_object, thread_id, thread_type, author_id)

        if self.is_undo_enabled(thread_id):
            if message_object.msgType == 'chat.undo':
                send_time = datetime.now()
                cliMsgId = str(message_object.content['cliMsgId'])
                stored_message = next((msg for msg in self.load_message_history() if str(msg.get('cliMsgId')) == cliMsgId), None)
                content_type = "tin nhắn"
                content = stored_message.get('text', '') if stored_message else ""

                if stored_message and stored_message['msgType'] == 'chat.sticker':
                    content_type = "sticker"
                    sticker_id, sticker_catId = self.TimTinNhanThuHoi(cliMsgId)
                    if sticker_id and sticker_catId:
                        author_name = self.fetchUserInfo(author_id).name
                        text = f'''@{author_name}\nĐã thu hồi sticker\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nSticker sẽ được gửi bên dưới'''
                        offset = text.find(f"@{author_name}")
                        self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
                        self.sendSticker(1, sticker_id, sticker_catId, thread_id, thread_type)

                elif stored_message and stored_message['msgType'] in ['image', 'photo'] and 'href' in stored_message['content']:
                    content_type = "hình ảnh"
                    anh_path = self.DownAnhThuHoi(stored_message['content']['thumb'])
                    if anh_path:
                        image = Image.open(anh_path)
                        width, height = image.size
                        author_name = self.fetchUserInfo(author_id).name
                        text = f'''@{author_name}\nĐã thu hồi hình ảnh\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nHình ảnh sẽ được gửi bên dưới'''
                        offset = text.find(f"@{author_name}")
                        self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
                        self.sendLocalImage(anh_path, width=width, height=height, thread_id=thread_id, thread_type=thread_type, ttl=720000)
                        os.remove(anh_path)

                elif stored_message and stored_message['msgType'] in ['video', 'share.video'] and 'href' in stored_message['content']:
                    content_type = "video"
                    video_content = self.Luuvideo(cliMsgId)
                    if video_content and 'href' in video_content:
                        thumb_url = video_content.get('thumb', '')
                        params = video_content.get('params', '{}')
                        try:
                            params_dict = json.loads(params)
                            duration = params_dict.get('duration', '')
                            width = params_dict.get('video_width', '')
                            height = params_dict.get('video_height', '')
                        except json.JSONDecodeError:
                            duration, width, height = '', '', ''
                        author_name = self.fetchUserInfo(author_id).name
                        text = f'''@{author_name}\nĐã thu hồi video\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nVideo sẽ được gửi bên dưới'''
                        offset = text.find(f"@{author_name}")
                        self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
                        self.sendRemoteVideo(video_content['href'], thumb_url, duration=duration, width=width, height=height, thread_id=thread_id, thread_type=thread_type, ttl=720000)

                elif stored_message and stored_message['msgType'] == 'chat.voice' and 'href' in stored_message['content']:
                    content_type = "giọng nói"
                    voice_url = stored_message['content']['href']
                    author_name = self.fetchUserInfo(author_id).name
                    text = f'''@{author_name}\nĐã thu hồi giọng nói\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nGiọng nói sẽ được gửi bên dưới'''
                    offset = text.find(f"@{author_name}")
                    self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
                    self.sendRemoteVoice(voice_url, thread_id, thread_type, fileSize=1)

                elif stored_message and stored_message['msgType'] == 'share.file' and 'href' in stored_message['content']:
                    content_type = "tệp tin"
                    file_url = stored_message['content']['href']
                    file_name = stored_message['text'].split("title='")[1].split("',")[0] if "title='" in stored_message['text'] else "Unknown"
                    file_extension = stored_message['content']['params'].split("fileExt\":\"")[1].split("\"")[0] if "fileExt\":\"" in stored_message['content']['params'] else ""
                    author_name = self.fetchUserInfo(author_id).name
                    text = f'''@{author_name}\nĐã thu hồi tệp tin\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nTệp tin sẽ được gửi bên dưới'''
                    offset = text.find(f"@{author_name}")
                    self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
                    self.sendRemoteFile(file_url, fileName=file_name, thread_id=thread_id, thread_type=thread_type, fileSize=None, extension=file_extension)

                elif stored_message and stored_message['msgType'] == 'chat.recommended' and 'params' in stored_message['content']:
                    content_type = "thẻ liên hệ"
                    user_id = stored_message['content']['params']
                    phone = stored_message['text'].split("phone\":\"")[1].split("\",")[0] if "phone\":\"" in stored_message['text'] else ""
                    author_name = self.fetchUserInfo(author_id).name
                    text = f'''@{author_name}\nĐã thu hồi thẻ liên hệ\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nThẻ liên hệ sẽ được gửi bên dưới'''
                    offset = text.find(f"@{author_name}")
                    self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
                    self.sendBusinessCard(userId=user_id, qrCodeUrl=None, thread_id=thread_id, thread_type=thread_type, phone=phone)

                else:
                    author_name = self.fetchUserInfo(author_id).name
                    text = f'''@{author_name}\nĐã thu hồi tin nhắn\nThời gian gửi: {send_time.strftime('%H:%M:%S %d/%m/%Y')}\nTin nhắn sẽ được gửi bên dưới'''
                    offset = text.find(f"@{author_name}")
                    self.replyMessage(Message(text=text, mention=Mention(author_id, len(f"@{author_name}"), offset)), message_object, thread_id, thread_type, ttl=720000)
                    if stored_message and 'text' in stored_message:
                        self.replyMessage(Message(text=content), message_object, thread_id, thread_type, ttl=720000)

            if message_object.msgType != 'chat.undo':
                self.LuuNoiDungThuHoi(message_object, message_text)

    def is_undo_enabled(self, thread_id):
        if thread_id in self.undo_enabled.get("groups", {}):
            return self.undo_enabled["groups"][thread_id]
        return False

    def load_locked_users(self):
        try:
            with open(self.locked_users_file, 'r') as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return []

    def save_locked_users(self):
        with open(self.locked_users_file, 'w') as f:
            json.dump(self.locked_users, f, indent=4)

    def load_message_history(self):
        try:
            with open(self.FileNM, 'r') as f:
                return json.load(f)
        except FileNotFoundError:
            return []
        except json.JSONDecodeError as e:
            logger.error(f"Lỗi đọc lịch sử tin nhắn: {e}")
            return []

    def handle_undo_command(self, message, message_object, thread_id, thread_type, author_id):
        self.undo_handler.handle_undo_command(message, message_object, thread_id, thread_type, author_id)

    def check_banned_words(self, message, message_object, thread_id, thread_type, author_id):
        if not self.loctk_enabled.get(thread_id, False):
            return
        if self.is_admin(author_id, thread_id):
            return
        normalized_message = unicodedata.normalize('NFKC', message).lower()
        for word in self.banned_words:
            normalized_word = unicodedata.normalize('NFKC', word).lower()
            if re.search(r'\b' + re.escape(normalized_word) + r'\b', normalized_message):
                self.deleteGroupMsg(message_object.msgId, author_id, message_object.cliMsgId, thread_id)
                self.record_banned_word_violation(author_id, thread_id, message_object)
                return

    def record_banned_word_violation(self, user_id, thread_id, message_object):
        if thread_id not in self.banned_word_violations:
            self.banned_word_violations[thread_id] = {}
        if user_id not in self.banned_word_violations[thread_id]:
            self.banned_word_violations[thread_id][user_id] = 0
        self.banned_word_violations[thread_id][user_id] += 1
        violations = self.banned_word_violations[thread_id][user_id]
        user_info = self.fetchUserInfo(user_id)
        if user_info and user_id in user_info.changed_profiles:
            user_name = user_info.changed_profiles[user_id].zaloName
        else:
            user_name = "Người dùng"
        if violations == 3:
            self.replyMessage(Message(text=f"Sắp cút rồi đấy."), message_object, thread_id, ThreadType.GROUP, ttl=12000)
        elif violations >= 5:
            self.blockUsersInGroup(user_id, thread_id)

    def should_kick_user(self, user_id, thread_id):
        if thread_id in self.banned_word_violations and user_id in self.banned_word_violations[thread_id]:
            return self.banned_word_violations[thread_id][user_id] >= 5
        return False

    def handle_loctk_command(self, message, message_object, thread_id, thread_type, author_id):
        if self.loctk_handler:
            self.loctk_handler.handle_loctk_command(message, message_object, thread_id, thread_type, author_id)
          
    def handle_antispam_command(self, message, message_object, thread_id, thread_type, author_id):
        if self.antispam_handler:
            self.antispam_handler.handle_antispam_command(message, message_object, thread_id, thread_type, author_id)

    def is_admin(self, user_id, thread_id):
        group_data = self.fetchGroupInfo(thread_id)
        if not group_data or thread_id not in group_data.gridInfoMap:
            return False
        group_data = group_data.gridInfoMap[thread_id]
        admins = group_data.get('adminIds', [])
        owners = group_data.get('creatorId')
        return user_id in admins or user_id == owners

    def check_spam(self, author_id, message_object, thread_id, thread_type):
        if not self.spam_enabled.get(thread_id, False):
            return
        if self.is_admin(author_id, thread_id):
            return

        now = time.time()
        if thread_id not in self.user_message_times:
            self.user_message_times[thread_id] = {}
        if author_id not in self.user_message_times[thread_id]:
            self.user_message_times[thread_id][author_id] = []
        user_messages = self.user_message_times[thread_id][author_id]
        user_messages.append(now)
        user_messages = [timestamp for timestamp in user_messages if now - timestamp <= self.spam_window]
        self.user_message_times[thread_id][author_id] = user_messages
        if len(user_messages) == self.spam_threshold:
            self.handle_spam_warn(author_id, message_object, thread_id, thread_type)
        elif len(user_messages) >= self.kick_threshold:
            self.handle_spam_violation(author_id, message_object, thread_id, thread_type)
            del self.user_message_times[thread_id][author_id]

    def handle_spam_warn(self, user_id, message_object, thread_id, thread_type):
        user_info = self.fetchUserInfo(user_id)
        if not user_info or user_id not in user_info.changed_profiles:
            return
        user_name = user_info.changed_profiles[user_id].zaloName
        msg = f"@{user_name} chậm thôi em zai"
        offset = msg.find(f"@{user_name}")
        self.replyMessage(Message(text=msg, mention=Mention(user_id, length=len(f"@{user_name}"), offset=offset)), message_object, thread_id, thread_type, ttl=30000)
        if user_id not in self.warned_users:
            self.warned_users[user_id] = {}
        self.warned_users[user_id][thread_id] = True

    def handle_spam_violation(self, user_id, message_object, thread_id, thread_type):
        user_info = self.fetchUserInfo(user_id)
        if not user_info or user_id not in user_info.changed_profiles:
          return
        user_name = user_info.changed_profiles[user_id].zaloName
        
        if self.is_admin(user_id, thread_id):
           return

        group_info = self.fetchGroupInfo(thread_id)
        if not group_info or thread_id not in group_info.gridInfoMap:
            self.send_error_message(thread_id, thread_type, "Không thể lấy thông tin nhóm.")
            return
        group_data = group_info.gridInfoMap[thread_id]
        creator_id = group_data.get('creatorId')
        admin_ids = group_data.get('adminIds', [])
        if self.uid not in admin_ids and self.uid != creator_id:
          return

        if user_id in admin_ids or user_id == creator_id:
            return

        self.blockUsersInGroup(user_id, thread_id)
        if user_id in self.warned_users and thread_id in self.warned_users[user_id]:
            del self.warned_users[user_id][thread_id]

    def cleanup_search_result(self, thread_id, author_id):
        if thread_id in self.search_results and author_id in self.search_results[thread_id]:
          del self.search_results[thread_id][author_id]
          if not self.search_results[thread_id]:
              del self.search_results[thread_id]
        if thread_id in self.search_result_messages and author_id in self.search_result_messages[thread_id]:
          del self.search_result_messages[thread_id][author_id]
          if not self.search_result_messages[thread_id]:
            del self.search_result_messages[thread_id]

    def cleanup_expired_search_results(self):
        while True:
            time.sleep(60)
            now = time.time()
            
            for thread_id in list(self.search_result_messages):
                for author_id in list(self.search_result_messages[thread_id]):
                    search_result_info = self.search_result_messages[thread_id][author_id]
                    elapsed_time = now - search_result_info['time']
                    if elapsed_time > self.search_result_ttl:
                        self.cleanup_search_result(thread_id, author_id)

    def handle_leave_command(self, message, message_object, thread_id, thread_type, author_id, client):
        if author_id not in ADMIN:
            msg = "Bạn không có quyền sử dụng lệnh này!"
            styles = MultiMsgStyle([
                MessageStyle(offset=0, length=10000, style="font", size="10", auto_format=False),
            ])
            client.replyMessage(Message(text=msg, style=styles), message_object, thread_id, thread_type, ttl=86400000)
            return
        try:
            # Gửi tin nhắn tạm biệt trước khi rời nhóm
            goodbye_msg = "Goodbye, đi đây nhé"
            styles = MultiMsgStyle([
                MessageStyle(offset=0, length=10000, style="font", size="10", auto_format=False),
            ])
            client.replyMessage(Message(text=goodbye_msg, style=styles), message_object, thread_id, thread_type, ttl=86400000)
            
            # Chờ 1 giây để đảm bảo tin nhắn được gửi
            time.sleep(1)
            
            # Thực hiện rời nhóm
            client.leaveGroup(thread_id, imei=IMEI)
            
        except ZaloAPIException as e:
            msg = f"err: {e}"
            styles = MultiMsgStyle([
                MessageStyle(offset=0, length=10000, style="font", size="10", auto_format=False),
            ])
            client.replyMessage(Message(text=msg, style=styles), message_object, thread_id, thread_type, ttl=86400000)
        except Exception as e:
            msg = f"error: {e}"
            styles = MultiMsgStyle([
                MessageStyle(offset=0, length=10000, style="font", size="10", auto_format=False),
            ])
            client.replyMessage(Message(text=msg, style=styles), message_object, thread_id, thread_type, ttl=86400000)

if __name__ == "__main__":
    try:
        client = Client(API_KEY, SECRET_KEY, IMEI, SESSION_COOKIES)
        client.listen(type="websocket", run_forever=True)
    except Exception as e:
        logger.error(f"Lỗi rồi, Không thể login...: {e}")
        python = sys.executable
        os.execl(python, python, *sys.argv)
        time.sleep(10)