279 lines
9.9 KiB
PHP
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();
|