import asyncio import os import logging from aiogram import Bot, Dispatcher, types, BaseMiddleware from aiogram.filters import Command from aiogram.types import FSInputFile, InlineKeyboardMarkup, InlineKeyboardButton from aiogram.client.default import DefaultBotProperties from aiogram.client.session.aiohttp import AiohttpSession # <-- TAMBAH INI from dotenv import load_dotenv from apscheduler.schedulers.asyncio import AsyncIOScheduler # Import modul lokal yang sudah kita buat from yt_engine import process_youtube_request from db_manager import db from s3_manager import upload_audio, download_audio, delete_audio # Setup Logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) load_dotenv() BOT_TOKEN = os.getenv("BOT_TOKEN") # Pastikan ini mengambil angka ID Telegram kamu AUTHORIZED_USER_ID = int(os.getenv("AUTHORIZED_USER_ID", 0)) # --- INISIALISASI BOT --- # Timeout diatur 300 detik (5 menit) agar Telegram tidak memutus koneksi saat mengirim file MP3 yang besar session = AiohttpSession(timeout=300) bot = Bot( token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=None) ) dp = Dispatcher() # --- MIDDLEWARE / SECURITY CHECK --- class SecurityMiddleware(BaseMiddleware): async def __call__(self, handler, event: types.Message, data: dict): # Memblokir semua orang kecuali Kamu (Single-User Mode) if event.from_user.id != AUTHORIZED_USER_ID: logger.warning(f"Akses ditolak untuk User ID: {event.from_user.id}") return # Abaikan pesan return await handler(event, data) # Daftarkan Satpam ke sistem dp.message.middleware(SecurityMiddleware()) # --- COMMAND HANDLERS --- @dp.message(Command("start")) async def cmd_start(message: types.Message): await message.answer( "๐ŸŽง **Kantor-Bypass Music Bot Ready!**\n\n" "Kirim judul lagu atau link YouTube dengan format:\n" "`/play `\n\n" "Contoh: `/play Dewa 19 Kangen`" ) @dp.message(Command("play")) async def cmd_play(message: types.Message): query = message.text.replace("/play", "").strip() if not query: return await message.answer("โš ๏ธ Masukkan judul lagu. Contoh: `/play Nadin Amizah`") # Kirim status loading status_msg = await message.answer("๐Ÿ” *Mencari dan memproses...*") # Gunakan asyncio.create_task agar tidak nge-block queue bot (bisa antre banyak lagu) asyncio.create_task(process_music_request(query, message.chat.id, status_msg.message_id)) # --- CORE LOGIC: PROCESS MUSIC --- async def process_music_request(query: str, chat_id: int, status_msg_id: int): local_file = None # Mencegah error 'UnboundLocalError' jika gagal di awal try: # 1. Download dari YouTube yt_result = await process_youtube_request(query) if yt_result["status"] == "error": return await bot.edit_message_text(f"โŒ Gagal: {yt_result['message']}", chat_id, status_msg_id) video_id = yt_result["video_id"] title = yt_result["title"] local_file = yt_result["file_path"] s3_object_name = f"{video_id}.mp3" # 2. Cek Cache di Database cached_data = await db.get_cache(video_id) if cached_data: await bot.edit_message_text("โšก *Mengambil dari Cache S3...*", chat_id, status_msg_id) # Download dari S3 ke local temporary local_file = f"downloads/cache_{video_id}.mp3" await download_audio(s3_object_name, local_file) else: await bot.edit_message_text("โ˜๏ธ *Mengunggah ke S3 Storage...*", chat_id, status_msg_id) # Upload file baru ke S3 if local_file and os.path.exists(local_file): await upload_audio(local_file, s3_object_name) await db.save_cache(video_id, title, s3_object_name) # 3. Kirim Audio ke Telegram await bot.edit_message_text("๐Ÿ“ค *Mengirim audio ke Telegram... (Mungkin butuh waktu)*", chat_id, status_msg_id) if local_file and os.path.exists(local_file): audio = FSInputFile(local_file) # Opsi: Tombol pencarian selanjutnya kb = [[InlineKeyboardButton(text="๐ŸŽต Putar Lagu Acak Lainnya", switch_inline_query_current_chat="")]] reply_markup = InlineKeyboardMarkup(inline_keyboard=kb) # Ekstra Pengaman Timeout di Request Level await bot.send_audio( chat_id=chat_id, audio=audio, title=title, performer="Music Bot", reply_markup=reply_markup, request_timeout=300 ) # Hapus pesan status yang muter-muter await bot.delete_message(chat_id, status_msg_id) else: await bot.edit_message_text("โŒ File audio tidak ditemukan di server lokal.", chat_id, status_msg_id) except Exception as e: logger.error(f"Error processing music: {e}") try: await bot.edit_message_text(f"โŒ Terjadi kesalahan internal: {e}", chat_id, status_msg_id) except: pass # Abaikan jika pesan sudah terhapus finally: # 4. Hapus file temporary di VPS (Hemat Disk!) if local_file and os.path.exists(local_file): os.remove(local_file) # --- BACKGROUND JOB: AUTO CLEANUP (7 HARI) --- async def cleanup_expired_cache(): logger.info("๐Ÿงน Menjalankan tugas pembersihan cache otomatis...") expired_items = await db.get_expired_cache(days=7) for item in expired_items: # Hapus dari S3 await delete_audio(item['s3_object_key']) # Hapus dari Database await db.delete_cache(item['youtube_id']) logger.info(f"๐Ÿ—‘๏ธ Dihapus: {item['title']} (Usia > 7 Hari)") # --- MAIN LOOP --- async def main(): # Konek ke database await db.connect() # Jalankan Scheduler untuk bersih-bersih tiap jam 3 pagi scheduler = AsyncIOScheduler() scheduler.add_job(cleanup_expired_cache, 'cron', hour=3, minute=0) scheduler.start() logger.info("๐Ÿš€ Bot Music Started!") # Hapus webhook lama (jika ada) dan mulai polling await bot.delete_webhook(drop_pending_updates=True) await dp.start_polling(bot) if __name__ == "__main__": asyncio.run(main())