167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
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 <judul/link>`\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())
|