Untitled

 avatar
unknown
plain_text
15 days ago
26 kB
3
Indexable
import sys
import time
import threading
import logging
from datetime import datetime
from pathlib import Path
import json

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                           QHBoxLayout, QPushButton, QLabel, QLineEdit, 
                           QTextEdit, QSpinBox, QMessageBox, QTabWidget,
                           QGroupBox, QFormLayout, QFileDialog, QCheckBox,
                           QComboBox, QProgressBar, QStatusBar)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QIcon, QFont, QTextCursor

# Import kelas RoyalDreamBot dari script asli
from royal_dream_botkencang_update10mart import RoyalDreamBot

# Kelas pekerja yang berjalan di thread terpisah untuk menjalankan bot
class BotWorker(QThread):
    signal_log = pyqtSignal(str)
    signal_progress = pyqtSignal(int)
    signal_status = pyqtSignal(str)
    
    def __init__(self, bot, check_interval=30):
        QThread.__init__(self)
        self.bot = bot
        self.check_interval = check_interval
        self.running = False
        self.processed_orders = 0
        
    def run(self):
        self.running = True
        self.signal_status.emit("Memulai...")
        
        # Hubungkan ke database
        if not self.bot.connect_database():
            self.signal_log.emit("โŒ Gagal menghubungkan ke database. Bot berhenti.")
            self.signal_status.emit("Disconnected")
            self.running = False
            return
        
        self.signal_log.emit("โœ… Berhasil terhubung ke database!")
        self.signal_status.emit("Connected")
        
        # Loop utama bot
        while self.running:
            try:
                # Refresh koneksi
                if not self.bot.conn or not self.bot.conn.is_connected():
                    self.signal_log.emit("๐Ÿ”„ Menyambungkan kembali ke database...")
                    self.bot.connect_database()
                
                # Dapatkan pesanan pending
                orders = self.bot.get_pending_orders()
                
                if orders:
                    self.signal_log.emit(f"๐Ÿ“ฆ Menemukan {len(orders)} pesanan pending")
                    
                    # Proses pesanan
                    for idx, order in enumerate(orders):
                        if not self.running:
                            break
                            
                        order_id = order['id']
                        user_id = order['data']
                        service_name = order['service_name']
                        
                        self.signal_log.emit(f"โณ Memproses pesanan #{order_id} untuk user ID {user_id} ({service_name})")
                        self.signal_status.emit(f"Memproses #{order_id}")
                        
                        # Update progress bar
                        progress_percent = int((idx + 1) / len(orders) * 100)
                        self.signal_progress.emit(progress_percent)
                        
                        success = self.bot.process_order(order)
                        if success:
                            self.processed_orders += 1
                            self.signal_log.emit(f"โœ… Pesanan #{order_id} berhasil diproses (total: {self.processed_orders})")
                        else:
                            self.signal_log.emit(f"โŒ Pesanan #{order_id} gagal diproses")
                        
                        # Jeda antar pesanan
                        time.sleep(1)
                    
                    self.signal_log.emit(f"โœ… Selesai memproses batch")
                    self.signal_status.emit("Idle")
                    self.signal_progress.emit(0)  # Reset progress bar
                else:
                    self.signal_log.emit(f"๐Ÿ’ค Tidak ada pesanan pending, menunggu {self.check_interval} detik...")
                    self.signal_status.emit("Menunggu")
                
                # Tunggu interval yang ditentukan
                for i in range(self.check_interval):
                    if not self.running:
                        break
                    # Update status setiap 5 detik
                    if i % 5 == 0:
                        self.signal_status.emit(f"Menunggu... ({self.check_interval - i})")
                    time.sleep(1)
                
            except Exception as e:
                self.signal_log.emit(f"โŒ Error dalam loop bot: {str(e)}")
                self.signal_status.emit("Error")
                time.sleep(5)
        
        self.signal_log.emit("๐Ÿ›‘ Bot dihentikan")
        self.signal_status.emit("Stopped")
    
    def stop(self):
        self.running = False
        self.signal_log.emit("๐Ÿ”„ Menghentikan bot, mohon tunggu...")
        self.wait(5000)  # Tunggu maksimal 5 detik

# Kelas untuk menangani output log
class LogHandler(logging.Handler):
    def __init__(self, signal_function):
        super().__init__()
        self.signal_function = signal_function
    
    def emit(self, record):
        log_message = self.format(record)
        self.signal_function.emit(log_message)

# Jendela utama aplikasi
class RoyalDreamBotUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.bot = None
        self.worker = None
        self.initUI()
        
        # Setup logging handler untuk UI
        self.log_handler = None
    
    def initUI(self):
        # Setup window utama
        self.setWindowTitle('Royal Dream Bot Manager')
        self.setGeometry(100, 100, 900, 700)
        
        # Widget utama dan layout
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        
        # Tab widget
        tab_widget = QTabWidget()
        main_layout.addWidget(tab_widget)
        
        # ---- TAB 1: Kontrol Bot ----
        tab_control = QWidget()
        tab_widget.addTab(tab_control, "Kontrol Bot")
        control_layout = QVBoxLayout(tab_control)
        
        # Grup Database
        db_group = QGroupBox("Konfigurasi Database")
        db_layout = QFormLayout()
        
        self.db_host = QLineEdit("103.186.1.63")
        self.db_port = QLineEdit("3306")
        self.db_user = QLineEdit("sql_kiosratu_id")
        self.db_pass = QLineEdit("f85a1d35ca69d")
        self.db_name = QLineEdit("sql_kiosratu_id")
        self.db_pass.setEchoMode(QLineEdit.Password)
        
        db_layout.addRow("Host:", self.db_host)
        db_layout.addRow("Port:", self.db_port)
        db_layout.addRow("Username:", self.db_user)
        db_layout.addRow("Password:", self.db_pass)
        db_layout.addRow("Database:", self.db_name)
        
        self.btn_test_conn = QPushButton("Tes Koneksi")
        self.btn_test_conn.clicked.connect(self.test_database_connection)
        db_layout.addRow("", self.btn_test_conn)
        
        db_group.setLayout(db_layout)
        control_layout.addWidget(db_group)
        
        # Grup Pengaturan Bot
        bot_group = QGroupBox("Pengaturan Bot")
        bot_layout = QFormLayout()
        
        self.check_interval = QSpinBox()
        self.check_interval.setRange(5, 300)
        self.check_interval.setValue(30)
        self.check_interval.setSuffix(" detik")
        
        coord_layout = QHBoxLayout()
        self.coord_path = QLineEdit()
        self.coord_path.setReadOnly(True)
        self.btn_browse_coord = QPushButton("Pilih...")
        self.btn_browse_coord.clicked.connect(self.browse_coord_file)
        coord_layout.addWidget(self.coord_path)
        coord_layout.addWidget(self.btn_browse_coord)
        
        bot_layout.addRow("Interval Cek:", self.check_interval)
        bot_layout.addRow("File Koordinat:", coord_layout)
        
        bot_group.setLayout(bot_layout)
        control_layout.addWidget(bot_group)
        
        # Grup Kontrol
        control_group = QGroupBox("Kontrol Bot")
        control_box = QHBoxLayout()
        
        self.btn_start = QPushButton("Mulai Bot")
        self.btn_start.setIcon(QIcon.fromTheme("media-playback-start"))
        self.btn_start.clicked.connect(self.start_bot)
        
        self.btn_stop = QPushButton("Hentikan Bot")
        self.btn_stop.setIcon(QIcon.fromTheme("media-playback-stop"))
        self.btn_stop.setEnabled(False)
        self.btn_stop.clicked.connect(self.stop_bot)
        
        self.btn_template = QPushButton("Buat Template")
        self.btn_template.setIcon(QIcon.fromTheme("document-new"))
        self.btn_template.clicked.connect(self.create_template)
        
        control_box.addWidget(self.btn_start)
        control_box.addWidget(self.btn_stop)
        control_box.addWidget(self.btn_template)
        
        control_group.setLayout(control_box)
        control_layout.addWidget(control_group)
        
        # Status area
        status_group = QGroupBox("Status")
        status_layout = QVBoxLayout()
        
        status_row = QHBoxLayout()
        status_row.addWidget(QLabel("Proses Batch:"))
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        status_row.addWidget(self.progress_bar)
        
        status_layout.addLayout(status_row)
        
        stat_info_layout = QHBoxLayout()
        stat_info_layout.addWidget(QLabel("Pesanan Diproses:"))
        self.order_count_label = QLabel("0")
        self.order_count_label.setStyleSheet("font-weight: bold;")
        stat_info_layout.addWidget(self.order_count_label)
        stat_info_layout.addStretch()
        
        status_layout.addLayout(stat_info_layout)
        status_group.setLayout(status_layout)
        control_layout.addWidget(status_group)
        
        # ---- TAB 2: Log ----
        tab_log = QWidget()
        tab_widget.addTab(tab_log, "Log")
        log_layout = QVBoxLayout(tab_log)
        
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setFont(QFont("Courier", 9))
        
        log_button_layout = QHBoxLayout()
        self.btn_clear_log = QPushButton("Bersihkan Log")
        self.btn_clear_log.clicked.connect(self.clear_log)
        self.btn_save_log = QPushButton("Simpan Log")
        self.btn_save_log.clicked.connect(self.save_log)
        
        log_button_layout.addWidget(self.btn_clear_log)
        log_button_layout.addWidget(self.btn_save_log)
        
        log_layout.addWidget(self.log_text)
        log_layout.addLayout(log_button_layout)
        
        # ---- TAB 3: Bantuan ----
        tab_help = QWidget()
        tab_widget.addTab(tab_help, "Bantuan")
        help_layout = QVBoxLayout(tab_help)
        
        help_text = QTextEdit()
        help_text.setReadOnly(True)
        help_text.setHtml("""
        <h2>Bantuan Royal Dream Bot Manager</h2>
        
        <h3>Langkah-langkah Menjalankan Bot:</h3>
        <ol>
            <li><b>Persiapan:</b> Pastikan BlueStacks sudah berjalan dengan Royal Dream terbuka dan login</li>
            <li><b>Buat Template:</b> Klik tombol "Buat Template" dan ikuti instruksi untuk mengkalibrasi koordinat UI</li>
            <li><b>Konfigurasi:</b> Periksa pengaturan database dan interval pengecekan</li>
            <li><b>Jalankan Bot:</b> Klik tombol "Mulai Bot" untuk memulai operasi</li>
            <li><b>Monitoring:</b> Pantau status dan log aktivitas bot pada tab "Log"</li>
        </ol>
        
        <h3>Tips Penting:</h3>
        <ul>
            <li>Jangan menggerakkan mouse atau menggunakan BlueStacks saat bot sedang berjalan</li>
            <li>Jika bot tidak menemukan elemen UI, kalibrasi ulang dengan "Buat Template"</li>
            <li>Interval yang direkomendasikan adalah 30 detik</li>
            <li>Gunakan tombol "Hentikan Bot" untuk menghentikan operasi dengan aman</li>
        </ul>
        
        <h3>Pemecahan Masalah:</h3>
        <ul>
            <li><b>Bot gagal menemukan elemen:</b> Kalibrasi ulang koordinat UI</li>
            <li><b>Koneksi database terputus:</b> Periksa pengaturan database dan jaringan</li>
            <li><b>Screenshot gagal:</b> Pastikan folder screenshot tersedia dan dapat diakses</li>
            <li><b>Bot crash:</b> Periksa log untuk detail masalah dan restart aplikasi</li>
        </ul>
        """)
        help_layout.addWidget(help_text)
        
        # Status Bar
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.status_label = QLabel("Siap")
        self.statusBar.addWidget(self.status_label)
        
        # Cari file koordinat di lokasi default
        default_coord_path = Path("C:/BOTRD/templates/coordinates.json")
        if default_coord_path.exists():
            self.coord_path.setText(str(default_coord_path))
            self.log("โœ… File koordinat ditemukan di lokasi default")
        
        # Tampilkan pesan selamat datang
        self.log("๐Ÿš€ Royal Dream Bot Manager v1.0")
        self.log("โš ๏ธ Pastikan BlueStack sudah berjalan dengan Royal Dream terbuka")
        self.log("๐Ÿ’ก Klik 'Buat Template' jika ini pertama kali menjalankan bot")
        self.log("๐Ÿ“ Klik 'Mulai Bot' setelah konfigurasi siap")
    
    def log(self, message):
        timestamp = datetime.now().strftime("%H:%M:%S")
        log_entry = f"[{timestamp}] {message}"
        self.log_text.append(log_entry)
        # Scroll ke bawah
        self.log_text.moveCursor(QTextCursor.End)
    
    def clear_log(self):
        self.log_text.clear()
        self.log("๐Ÿงน Log dibersihkan")
    
    def save_log(self):
        filename, _ = QFileDialog.getSaveFileName(
            self, "Simpan File Log", "", "Log Files (*.log);;Text Files (*.txt);;All Files (*)"
        )
        
        if filename:
            try:
                with open(filename, 'w', encoding='utf-8') as file:
                    file.write(self.log_text.toPlainText())
                self.log(f"โœ… Log berhasil disimpan ke {filename}")
            except Exception as e:
                self.log(f"โŒ Gagal menyimpan log: {e}")
                QMessageBox.critical(self, "Error", f"Gagal menyimpan log: {str(e)}")
    
    def test_database_connection(self):
        import mysql.connector
        
        self.log("๐Ÿ”„ Menguji koneksi database...")
        
        db_config = {
            'host': self.db_host.text(),
            'port': int(self.db_port.text()),
            'user': self.db_user.text(),
            'password': self.db_pass.text(),
            'database': self.db_name.text()
        }
        
        try:
            conn = mysql.connector.connect(**db_config)
            if conn.is_connected():
                server_info = conn.get_server_info()
                self.log(f"โœ… Berhasil terhubung ke database MySQL (versi {server_info})")
                
                cursor = conn.cursor()
                cursor.execute("SELECT DATABASE();")
                db_name = cursor.fetchone()[0]
                self.log(f"โœ… Database aktif: {db_name}")
                
                cursor.close()
                conn.close()
                
                QMessageBox.information(self, "Koneksi Berhasil", 
                                        f"Berhasil terhubung ke database MySQL (versi {server_info})\nDatabase aktif: {db_name}")
            else:
                self.log("โŒ Gagal terhubung ke database")
                QMessageBox.critical(self, "Koneksi Gagal", "Gagal terhubung ke database")
        except Exception as e:
            self.log(f"โŒ Error koneksi database: {str(e)}")
            QMessageBox.critical(self, "Error Koneksi", f"Error: {str(e)}")
    
    def browse_coord_file(self):
        filename, _ = QFileDialog.getOpenFileName(
            self, "Pilih File Koordinat", "C:/BOTRD/templates", "JSON Files (*.json);;All Files (*)"
        )
        
        if filename:
            self.coord_path.setText(filename)
            self.log(f"๐Ÿ“ File koordinat dipilih: {filename}")
    
    def create_template(self):
        # Konfirmasi terlebih dahulu
        reply = QMessageBox.question(self, "Konfirmasi", 
                                    "Pastikan BlueStack sudah berjalan dengan Royal Dream terbuka dan siap untuk kalibrasi.\n\nLanjutkan?",
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        
        if reply == QMessageBox.No:
            return
        
        # Inisialisasi bot untuk template
        self.log("๐Ÿš€ Memulai pembuatan template...")
        
        db_config = {
            'host': self.db_host.text(),
            'port': int(self.db_port.text()),
            'user': self.db_user.text(),
            'password': self.db_pass.text(),
            'database': self.db_name.text()
        }
        
        try:
            self.bot = RoyalDreamBot(db_config)
            
            # Mengatur custom log handler untuk menampilkan log di UI
            self.setup_log_handler()
            
            # Jalankan pembuatan template
            self.log("โš ๏ธ Ikuti instruksi di jendela console untuk membuat template")
            self.log("โš ๏ธ Jangan tutup aplikasi ini selama proses berlangsung")
            
            QMessageBox.information(self, "Perhatian", 
                                   "Proses pembuatan template akan dimulai di jendela console.\n\nLihat jendela console untuk mengikuti instruksi, lalu kembali ke aplikasi ini setelah selesai.")
            
            # Jalankan pembuatan template
            success = self.bot.create_template_images()
            
            if success:
                self.log("โœ… Template berhasil dibuat!")
                # Update path koordinat
                coord_path = Path("C:/BOTRD/templates/coordinates.json")
                if coord_path.exists():
                    self.coord_path.setText(str(coord_path))
                    
                QMessageBox.information(self, "Sukses", "Template berhasil dibuat!\n\nKoordinat UI telah disimpan dan siap digunakan.")
            else:
                self.log("โŒ Gagal membuat template")
                QMessageBox.critical(self, "Gagal", "Gagal membuat template. Lihat log untuk detail.")
                
        except Exception as e:
            self.log(f"โŒ Error saat membuat template: {str(e)}")
            QMessageBox.critical(self, "Error", f"Error saat membuat template: {str(e)}")
    
    def setup_log_handler(self):
        if self.bot:
            # Hapus handler yang sudah ada jika ada
            logger = logging.getLogger("RoyalDreamBot")
            if self.log_handler in logger.handlers:
                logger.removeHandler(self.log_handler)
            
            # Buat handler baru
            self.log_handler = LogHandler(self.log)
            self.log_handler.setFormatter(logging.Formatter('%(message)s'))
            logger.addHandler(self.log_handler)
    
    def start_bot(self):
        # Validasi konfigurasi
        if not Path(self.coord_path.text()).exists():
            QMessageBox.critical(self, "Error", "File koordinat tidak ditemukan. Silakan buat template terlebih dahulu.")
            return
        
        # Cek apakah bot sudah berjalan
        if self.worker and self.worker.isRunning():
            QMessageBox.warning(self, "Peringatan", "Bot sudah berjalan!")
            return
        
        # Konfirmasi
        reply = QMessageBox.question(self, "Konfirmasi", 
                                    "Pastikan BlueStack sudah berjalan dengan Royal Dream terbuka.\n\nMulai bot sekarang?",
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        
        if reply == QMessageBox.No:
            return
        
        # Setup konfigurasi database
        db_config = {
            'host': self.db_host.text(),
            'port': int(self.db_port.text()),
            'user': self.db_user.text(),
            'password': self.db_pass.text(),
            'database': self.db_name.text()
        }
        
        try:
            # Buat instance bot baru
            self.bot = RoyalDreamBot(db_config)
            
            # Muat koordinat dari file
            try:
                with open(self.coord_path.text(), 'r') as f:
                    self.bot.coordinates = json.load(f)
                self.log("โœ… Koordinat berhasil dimuat dari file")
            except Exception as e:
                self.log(f"โŒ Error memuat koordinat: {str(e)}")
                QMessageBox.critical(self, "Error", f"Gagal memuat file koordinat: {str(e)}")
                return
            
            # Mengatur log handler
            self.setup_log_handler()
            
            # Buat worker thread
            self.worker = BotWorker(self.bot, self.check_interval.value())
            
            # Hubungkan sinyal
            self.worker.signal_log.connect(self.log)
            self.worker.signal_progress.connect(self.progress_bar.setValue)
            self.worker.signal_status.connect(self.update_status)
            
            # Tambahkan koneksi untuk menangani jumlah pesanan yang diproses
            def update_processed_count():
                if self.bot:
                    self.order_count_label.setText(str(self.bot.processed_count))
            
            # Timer untuk update jumlah pesanan
            self.order_count_timer = threading.Timer(1.0, update_processed_count)
            self.order_count_timer.daemon = True
            self.order_count_timer.start()
            
            # Mulai thread
            self.worker.start()
            
            # Update UI
            self.btn_start.setEnabled(False)
            self.btn_stop.setEnabled(True)
            self.btn_template.setEnabled(False)
            
            self.log("๐Ÿš€ Bot dimulai dengan interval " + str(self.check_interval.value()) + " detik")
            
        except Exception as e:
            self.log(f"โŒ Error memulai bot: {str(e)}")
            QMessageBox.critical(self, "Error", f"Gagal memulai bot: {str(e)}")
    
    def stop_bot(self):
        if self.worker and self.worker.isRunning():
            self.log("๐Ÿ›‘ Menghentikan bot... (mohon tunggu)")
            self.worker.stop()
            
            # Nonaktifkan tombol stop untuk mencegah klik ganda
            self.btn_stop.setEnabled(False)
            
            # Tunggu hingga thread selesai (maksimal 5 detik)
            if not self.worker.wait(5000):
                self.log("โš ๏ธ Terpaksa mengakhiri thread bot")
                self.worker.terminate()
            
            # Update UI
            self.btn_start.setEnabled(True)
            self.btn_stop.setEnabled(False)
            self.btn_template.setEnabled(True)
            
            self.log("๐Ÿ›‘ Bot dihentikan")
            self.update_status("Dihentikan")
            self.progress_bar.setValue(0)
            
            # Hentikan timer
            if hasattr(self, 'order_count_timer') and self.order_count_timer:
                self.order_count_timer.cancel()
    
    def update_status(self, status):
        self.status_label.setText(status)
    
    def closeEvent(self, event):
        # Hentikan bot saat aplikasi ditutup
        if self.worker and self.worker.isRunning():
            reply = QMessageBox.question(self, "Konfirmasi", 
                                        "Bot masih berjalan. Hentikan dan keluar?",
                                        QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            
            if reply == QMessageBox.Yes:
                self.log("๐Ÿ›‘ Menghentikan bot karena aplikasi ditutup...")
                self.worker.stop()
                
                # Tunggu hingga thread selesai (maksimal 2 detik)
                if not self.worker.wait(2000):
                    self.worker.terminate()
                
                event.accept()
            else:
                event.ignore()
        else:
            event.accept()

# Entri utama aplikasi
if __name__ == "__main__":
    try:
        # Buat direktori log jika belum ada
        log_dir = Path("C:/BOTRD")
        log_dir.mkdir(exist_ok=True)
        
        app = QApplication(sys.argv)
        window = RoyalDreamBotUI()
        window.show()
        sys.exit(app.exec_())
    except Exception as e:
        # Tangkap error untuk mencegah crash tanpa pemberitahuan
        error_path = Path("C:/BOTRD/error_log.txt")
        with open(error_path, "w") as f:
            f.write(f"Error saat menjalankan aplikasi: {str(e)}\n")
            import traceback
            f.write(traceback.format_exc())
            
        # Tampilkan pesan error dalam dialog
        if QApplication.instance():
            QMessageBox.critical(None, "Error Fatal", 
                                f"Terjadi error fatal: {str(e)}\n\nDetail error tersimpan di: {error_path}")
Editor is loading...
Leave a Comment