38431-vm/bot.php
2026-02-14 17:38:02 +00:00

279 lines
9.9 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/db/config.php';
use Discord\Discord;
use Discord\Voice\VoiceClient;
use Discord\Parts\Channel\Channel;
use Discord\Parts\User\Member;
use Discord\WebSockets\Event;
use Discord\WebSockets\Intents;
use Discord\Builders\CommandBuilder;
use Discord\Builders\MessageBuilder;
use Discord\Parts\Interactions\Interaction;
use Discord\Parts\Interactions\Command\Option;
$db = db();
$settings = $db->query("SELECT setting_key, setting_value FROM bot_settings")->fetchAll(PDO::FETCH_KEY_PAIR);
$token = $settings['bot_token'] ?? '';
$vcId = $settings['voice_channel_id'] ?? '1457687430189682781';
$sahurTime = $settings['sahur_time'] ?? '03:00';
$sahurSource = $settings['sahur_source'] ?? 'sahur.mp3';
if (empty($token)) {
echo "Error: Bot token is missing in settings.\n";
exit(1);
}
$discord = new Discord([
'token' => $token,
'intents' => Intents::getDefaultIntents() | Intents::GUILD_VOICE_STATES | Intents::MESSAGE_CONTENT,
]);
$voiceClient = null;
$queue = [];
$currentTrack = null;
function logToDb($message, $level = 'info') {
$db = db();
$stmt = $db->prepare("INSERT INTO bot_logs (message, log_level) VALUES (?, ?)");
$stmt->execute([$message, $level]);
}
$discord->on('ready', function (Discord $discord) use (&$voiceClient, $vcId, $sahurTime) {
echo "Bot is ready!", PHP_EOL;
logToDb("Bot is online and ready.");
// Update status in DB
$db = db();
$db->prepare("UPDATE bot_settings SET setting_value = 'online' WHERE setting_key = 'bot_status'")->execute();
// Auto-join VC
joinVoiceChannel($discord, $vcId);
// Schedule Sahur
$discord->getLoop()->addPeriodicTimer(60, function () use ($discord, $sahurTime, $vcId) {
$now = date('H:i');
if ($now === $sahurTime) {
echo "Sahur time! Playing audio...\n";
logToDb("Sahur time triggered. Playing audio.");
playSahur($discord, $vcId);
}
});
// Register Slash Commands
registerCommands($discord);
});
function joinVoiceChannel(Discord $discord, $channelId, $interaction = null) {
global $voiceClient;
if ($voiceClient) {
$msg = "I am already in a voice channel: " . ($voiceClient->getChannel()->name ?? 'Unknown');
if ($interaction) {
$interaction->respondWithMessage(MessageBuilder::new()->setContent($msg));
}
return;
}
$channel = $discord->getChannel($channelId);
// If channel not in cache, try to find it in guilds
if (!$channel) {
foreach ($discord->guilds as $guild) {
$channel = $guild->channels->get('id', $channelId);
if ($channel) break;
}
}
if ($channel instanceof Channel && $channel->type === Channel::TYPE_VOICE) {
if ($interaction) {
$interaction->respondWithMessage(MessageBuilder::new()->setContent("Connecting to voice channel: " . $channel->name . "..."));
}
$discord->joinVoiceChannel($channel)->then(function (VoiceClient $vc) use (&$voiceClient, $discord, $channelId, $channel, $interaction) {
$voiceClient = $vc;
echo "Joined voice channel: " . $channel->name, PHP_EOL;
logToDb("Joined voice channel: " . $channel->name);
if ($interaction) {
$interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Successfully joined voice channel: " . $channel->name));
}
$vc->on('error', function ($e) use ($discord, $channelId) {
echo "Voice Error: " . $e->getMessage(), PHP_EOL;
logToDb("Voice error: " . $e->getMessage(), 'error');
});
}, function ($e) use ($interaction) {
$errorMsg = "Could not join voice channel: " . $e->getMessage();
echo $errorMsg, PHP_EOL;
logToDb($errorMsg, 'error');
if ($interaction) {
try {
$interaction->updateOriginalResponse(MessageBuilder::new()->setContent($errorMsg));
} catch (\Exception $ex) {
// Fallback if update fails
}
}
});
} else {
$errorMsg = "Voice channel not found or invalid ID: " . $channelId;
if ($interaction) {
$interaction->respondWithMessage(MessageBuilder::new()->setContent($errorMsg));
}
echo $errorMsg, PHP_EOL;
}
}
function playSahur(Discord $discord, $vcId) {
global $voiceClient, $sahurSource;
if (!$voiceClient) {
joinVoiceChannel($discord, $vcId);
// Wait a bit for connection
$discord->getLoop()->addTimer(3, function() use ($discord, $vcId) {
playSahur($discord, $vcId);
});
return;
}
if (filter_var($sahurSource, FILTER_VALIDATE_URL)) {
echo "Playing sahur from URL: $sahurSource\n";
$cmd = "yt-dlp -g -f bestaudio \"$sahurSource\"";
exec($cmd, $output, $resultCode);
if ($resultCode === 0 && !empty($output[0])) {
$voiceClient->playRawStream($output[0]);
}
} else {
if (!file_exists($sahurSource)) {
$error = "Error: Audio file '$sahurSource' not found. Please upload it to the bot's root directory.";
echo $error, PHP_EOL;
logToDb($error, 'error');
return;
}
echo "Playing sahur from file: $sahurSource\n";
$voiceClient->playFile($sahurSource)->done(function() {
echo "Finished playing sahur audio.\n";
});
}
}
function registerCommands(Discord $discord) {
$commands = [
CommandBuilder::new()
->setName('join')
->setDescription('Join the default voice channel'),
CommandBuilder::new()
->setName('out')
->setDescription('Leave the voice channel'),
CommandBuilder::new()
->setName('status')
->setDescription('Check bot status'),
CommandBuilder::new()
->setName('testsahur')
->setDescription('Test play sahur audio now'),
CommandBuilder::new()
->setName('play')
->setDescription('Play music from URL')
->addOption((new Option($discord))
->setName('url')
->setDescription('The URL of the song (YouTube, SoundCloud, etc.)')
->setType(Option::STRING)
->setRequired(true)),
CommandBuilder::new()
->setName('skip')
->setDescription('Skip current song'),
CommandBuilder::new()
->setName('stop')
->setDescription('Stop music and clear queue'),
];
foreach ($commands as $command) {
$discord->application->commands->save(
$discord->application->commands->create($command->toArray())
);
}
}
$discord->on(Event::INTERACTION_CREATE, function (Interaction $interaction, Discord $discord) use (&$voiceClient, $vcId) {
$command = $interaction->data->name;
switch ($command) {
case 'join':
$userChannel = $interaction->member->getVoiceChannel();
if ($userChannel) {
joinVoiceChannel($discord, $userChannel->id, $interaction);
} else {
joinVoiceChannel($discord, $vcId, $interaction);
}
break;
case 'out':
if ($voiceClient) {
$channelName = $voiceClient->getChannel()->name ?? 'channel';
$voiceClient->close();
$voiceClient = null;
$interaction->respondWithMessage(MessageBuilder::new()->setContent("Left voice channel: $channelName."));
} else {
$interaction->respondWithMessage(MessageBuilder::new()->setContent("I'm not in a voice channel."));
}
break;
case 'status':
$vcStatus = $voiceClient ? "Connected to: " . ($voiceClient->getChannel()->name ?? 'Unknown') : "Not connected to any voice channel.";
$status = "Bot is online.\n$vcStatus\nTarget default VC: $vcId";
$interaction->respondWithMessage(MessageBuilder::new()->setContent($status));
break;
case 'testsahur':
playSahur($discord, $vcId);
$interaction->respondWithMessage(MessageBuilder::new()->setContent("Testing sahur audio..."));
break;
case 'play':
$url = $interaction->data->options['url']->value;
handlePlay($interaction, $discord, $url);
break;
case 'skip':
if ($voiceClient) {
$voiceClient->stop();
$interaction->respondWithMessage(MessageBuilder::new()->setContent("Skipped."));
}
break;
case 'stop':
global $queue;
$queue = [];
if ($voiceClient) {
$voiceClient->stop();
}
$interaction->respondWithMessage(MessageBuilder::new()->setContent("Stopped and cleared queue."));
break;
}
});
function handlePlay(Interaction $interaction, Discord $discord, string $url) {
global $voiceClient, $queue, $vcId;
if (!$voiceClient) {
$interaction->respondWithMessage(MessageBuilder::new()->setContent("I need to be in a voice channel first. use `/join`"));
return;
}
$interaction->acknowledge();
// Simple play logic using yt-dlp
$cmd = "yt-dlp -g -f bestaudio \"$url\"";
exec($cmd, $output, $resultCode);
if ($resultCode === 0 && !empty($output[0])) {
$audioUrl = $output[0];
$voiceClient->playRawStream($audioUrl)->done(function() use ($interaction) {
// Logic for next in queue could go here
});
$interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Now playing: $url"));
} else {
$interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Error: Could not fetch audio for that URL."));
}
}
$discord->run();