Autosave: 20260217-074955
This commit is contained in:
parent
c0a959cfc7
commit
bc6c1b2066
18
api/reset_bot.php
Normal file
18
api/reset_bot.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid request method']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = [];
|
||||||
|
$return_var = 0;
|
||||||
|
// Using sudo to run as ubuntu user who owns the pm2 process
|
||||||
|
exec("sudo -u ubuntu /usr/bin/pm2 restart discord-bot 2>&1", $output, $return_var);
|
||||||
|
|
||||||
|
if ($return_var === 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Bot restarted successfully']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Failed to restart bot', 'details' => implode("\n", $output)]);
|
||||||
|
}
|
||||||
@ -2,6 +2,6 @@
|
|||||||
"discord_token": "MTQ3MTkwOTE5Mzg4Njg1OTI5NA.GCEdpc.jMIxPFsquVAhp88x3dO-yWUFI7e1u1r8oIZTcw",
|
"discord_token": "MTQ3MTkwOTE5Mzg4Njg1OTI5NA.GCEdpc.jMIxPFsquVAhp88x3dO-yWUFI7e1u1r8oIZTcw",
|
||||||
"guild_id": "1428530728706117632",
|
"guild_id": "1428530728706117632",
|
||||||
"voice_channel_id": "1457687430189682781",
|
"voice_channel_id": "1457687430189682781",
|
||||||
"alarm_time": "09:20",
|
"alarm_time": "15:33",
|
||||||
"last_voice_channel": null
|
"last_voice_channel": null
|
||||||
}
|
}
|
||||||
134
index.js
134
index.js
@ -1,13 +1,15 @@
|
|||||||
const ffmpeg = require('ffmpeg-static');
|
process.env.FFMPEG_PATH = require('ffmpeg-static');
|
||||||
process.env.FFMPEG_PATH = ffmpeg;
|
require('opusscript');
|
||||||
const { Client, GatewayIntentBits, SlashCommandBuilder, Routes, ActivityType } = require('discord.js');
|
require('libsodium-wrappers');
|
||||||
const { joinVoiceChannel, createAudioPlayer, createAudioResource, getVoiceConnection, VoiceConnectionStatus, StreamType } = require('@discordjs/voice');
|
const { Client, GatewayIntentBits, SlashCommandBuilder, Routes } = require('discord.js');
|
||||||
const { REST } = require('@discordjs/rest');
|
const { joinVoiceChannel, createAudioPlayer, createAudioResource, StreamType, getVoiceConnection } = require('@discordjs/voice');
|
||||||
const { join } = require('path');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { REST } = require('@discordjs/rest');
|
||||||
|
|
||||||
// Load Config
|
// Load Config
|
||||||
const config = JSON.parse(fs.readFileSync(join(__dirname, 'data/config.json'), 'utf8'));
|
const configPath = path.join(__dirname, 'data/config.json');
|
||||||
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
const token = config.discord_token;
|
const token = config.discord_token;
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
@ -16,47 +18,89 @@ const client = new Client({
|
|||||||
|
|
||||||
const player = createAudioPlayer();
|
const player = createAudioPlayer();
|
||||||
|
|
||||||
|
// Monitoring Status
|
||||||
|
player.on('stateChange', (oldState, newState) => {
|
||||||
|
console.log(`Status Player: ${newState.status}`);
|
||||||
|
});
|
||||||
|
|
||||||
player.on('error', error => {
|
player.on('error', error => {
|
||||||
console.error('Error Audio:', error);
|
console.log('ERROR AUDIO:', error.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('ready', async () => {
|
client.on('ready', async () => {
|
||||||
console.log(`Bot logged in as ${client.user.tag}`);
|
console.log(`Bot Sahur Online: ${client.user.tag}`);
|
||||||
|
|
||||||
// Set Status Online dan Aktivitas
|
|
||||||
client.user.setPresence({
|
|
||||||
activities: [{ name: 'Lilis', type: ActivityType.Watching }],
|
|
||||||
status: 'online',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register Commands
|
|
||||||
const commands = [
|
const commands = [
|
||||||
new SlashCommandBuilder().setName('join').setDescription('Bot masuk ke Voice Channel'),
|
new SlashCommandBuilder().setName('join').setDescription('Perintah untuk bot masuk ke Voice Channel'),
|
||||||
new SlashCommandBuilder().setName('testsahur').setDescription('Test audio sahur')
|
new SlashCommandBuilder().setName('testsahur').setDescription('Perintah untuk memutar suara alarm sahur')
|
||||||
].map(cmd => cmd.toJSON());
|
].map(cmd => cmd.toJSON());
|
||||||
|
|
||||||
const rest = new REST({ version: '10' }).setToken(token);
|
const rest = new REST({ version: '10' }).setToken(token);
|
||||||
try {
|
try {
|
||||||
await rest.put(Routes.applicationCommands(client.user.id), { body: commands });
|
await rest.put(Routes.applicationCommands(client.user.id), { body: commands });
|
||||||
console.log('Slash commands registered.');
|
console.log('Slash Commands Berhasil Didaftarkan.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to register commands:', err);
|
console.error('Gagal daftar command:', err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('interactionCreate', async interaction => {
|
client.on('interactionCreate', async interaction => {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
|
// 1. PERINTAH JOIN (Hanya Masuk)
|
||||||
if (interaction.commandName === 'join') {
|
if (interaction.commandName === 'join') {
|
||||||
await interaction.deferReply(); // Baris PERTAMA
|
await interaction.deferReply();
|
||||||
console.log('Command dijalankan... /join');
|
|
||||||
|
|
||||||
const channel = interaction.member.voice.channel;
|
const channel = interaction.member.voice.channel;
|
||||||
if (!channel) return interaction.editReply('Masuk ke Voice Channel dulu!');
|
|
||||||
|
|
||||||
// Clean Connection Logic: Cek koneksi yang sudah ada
|
if (!channel) {
|
||||||
|
return interaction.editReply('❌ Kamu harus masuk ke Voice Channel dulu!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = joinVoiceChannel({
|
||||||
|
channelId: channel.id,
|
||||||
|
guildId: interaction.guildId,
|
||||||
|
adapterCreator: interaction.guild.voiceAdapterCreator,
|
||||||
|
selfDeaf: false,
|
||||||
|
selfMute: false,
|
||||||
|
debug: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep-Alive Logic
|
||||||
|
connection.on('stateChange', (oldState, newState) => {
|
||||||
|
if (newState.status === 'disconnected') {
|
||||||
|
console.log('Bot terputus, mencoba masuk kembali...');
|
||||||
|
try {
|
||||||
|
joinVoiceChannel({
|
||||||
|
channelId: channel.id,
|
||||||
|
guildId: interaction.guildId,
|
||||||
|
adapterCreator: interaction.guild.voiceAdapterCreator,
|
||||||
|
selfDeaf: false,
|
||||||
|
selfMute: false
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Gagal rejoin:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Bot masuk voice!');
|
||||||
|
await interaction.editReply('✅ Bot sudah masuk ke Voice Channel. Siap untuk /testsahur!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. PERINTAH TESTSAHUR (Auto-Join & Play)
|
||||||
|
if (interaction.commandName === 'testsahur') {
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
let connection = getVoiceConnection(interaction.guildId);
|
let connection = getVoiceConnection(interaction.guildId);
|
||||||
|
const channel = interaction.member.voice.channel;
|
||||||
|
|
||||||
|
// Auto-Reconnect: Jika tidak ada koneksi, join dulu
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
|
if (!channel) {
|
||||||
|
return interaction.editReply('❌ Bot tidak di VC dan kamu juga tidak di VC. Join VC dulu!');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Connection tidak ditemukan, mencoba auto-join...');
|
||||||
connection = joinVoiceChannel({
|
connection = joinVoiceChannel({
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
guildId: interaction.guildId,
|
guildId: interaction.guildId,
|
||||||
@ -64,44 +108,30 @@ client.on('interactionCreate', async interaction => {
|
|||||||
selfDeaf: false,
|
selfDeaf: false,
|
||||||
selfMute: false
|
selfMute: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe player immediately
|
|
||||||
connection.subscribe(player);
|
|
||||||
|
|
||||||
// Stay 24/7: Pastikan bot tidak disconnect
|
|
||||||
connection.on(VoiceConnectionStatus.Disconnected, () => {
|
|
||||||
console.log('Bot terputus dari voice channel.');
|
|
||||||
});
|
|
||||||
|
|
||||||
await interaction.editReply('Berhasil join ke Voice Channel dan Standby 24/7!');
|
|
||||||
} else {
|
|
||||||
await interaction.editReply('Bot sudah berada di dalam Voice Channel.');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.commandName === 'testsahur') {
|
|
||||||
await interaction.deferReply(); // Baris PERTAMA
|
|
||||||
console.log('Command dijalankan... /testsahur');
|
|
||||||
|
|
||||||
const connection = getVoiceConnection(interaction.guildId);
|
|
||||||
if (!connection) return interaction.editReply('Bot belum join! Gunakan /join dulu.');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Audio Fix as requested with Volume Booster
|
const audioPath = path.join(process.cwd(), 'assets', 'audio', 'sahur.mp3');
|
||||||
const resource = createAudioResource(join(__dirname, 'assets', 'audio', 'sahur.mp3'), {
|
console.log('Membaca file audio di:', audioPath);
|
||||||
|
|
||||||
|
if (!fs.existsSync(audioPath)) {
|
||||||
|
return interaction.editReply('❌ File audio tidak ditemukan!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = createAudioResource(audioPath, {
|
||||||
inputType: StreamType.Arbitrary,
|
inputType: StreamType.Arbitrary,
|
||||||
inlineVolume: true
|
inlineVolume: true
|
||||||
});
|
});
|
||||||
resource.volume.setVolume(1.0);
|
|
||||||
|
|
||||||
|
resource.volume.setVolume(1.5);
|
||||||
connection.subscribe(player);
|
connection.subscribe(player);
|
||||||
player.play(resource);
|
player.play(resource);
|
||||||
|
|
||||||
// Edit Reply as requested
|
console.log('Kabel audio tersambung dan suara diputar!');
|
||||||
await interaction.editReply('Memutar audio sahur... 🔊');
|
await interaction.editReply('🔊 Sedang memutar alarm sahur (Auto-Connect Aktif)...');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error playing audio:', error);
|
console.log('ERROR AUDIO:', error.message);
|
||||||
await interaction.editReply('Gagal memutar audio. Pastikan file sahur.mp3 ada di folder assets/audio/');
|
await interaction.editReply('❌ Terjadi kesalahan: ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
75
index.php
75
index.php
@ -127,18 +127,26 @@ $alarmTime = get_setting('alarm_time');
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<?php
|
<?php
|
||||||
|
$bot_status = 'Offline';
|
||||||
$bot_running = false;
|
$bot_running = false;
|
||||||
$output = [];
|
$pm2_output = [];
|
||||||
exec("ps aux | grep 'node index.js' | grep -v grep", $output);
|
exec("sudo -u ubuntu /usr/bin/pm2 jlist", $pm2_output);
|
||||||
if (!empty($output)) {
|
$pm2_data = json_decode(implode('', $pm2_output), true);
|
||||||
$bot_running = true;
|
if ($pm2_data) {
|
||||||
|
foreach ($pm2_data as $proc) {
|
||||||
|
if ($proc['name'] === 'discord-bot') {
|
||||||
|
$bot_status = ucfirst($proc['pm2_env']['status']);
|
||||||
|
$bot_running = ($proc['pm2_env']['status'] === 'online');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<header class="d-flex justify-content-between align-items-center mb-4">
|
<header class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1>Alarm Sahur Bot</h1>
|
<h1>Alarm Sahur Bot</h1>
|
||||||
<div class="small text-muted">
|
<div class="small text-muted">
|
||||||
<span class="status-dot <?= $bot_running ? 'status-online' : '' ?>"></span>
|
<span class="status-dot <?= $bot_running ? 'status-online' : '' ?>"></span>
|
||||||
<?= $bot_running ? 'Bot Online' : 'System Standby' ?>
|
Bot: <strong><?= $bot_status ?></strong>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -177,6 +185,18 @@ $alarmTime = get_setting('alarm_time');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">Bot Controls</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="small text-muted mb-3">Use these controls to manage the bot process. If the bot is not responding, try resetting it.</p>
|
||||||
|
<button id="resetBotBtn" class="btn btn-outline-danger w-100 fw-medium">
|
||||||
|
<span id="btnText">Reset Bot Process</span>
|
||||||
|
<span id="btnLoader" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<div id="resetStatus" class="hint mt-2 text-center d-none"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<span>Audio Asset</span>
|
<span>Audio Asset</span>
|
||||||
@ -210,5 +230,50 @@ $alarmTime = get_setting('alarm_time');
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('resetBotBtn').addEventListener('click', function() {
|
||||||
|
const btn = this;
|
||||||
|
const btnText = document.getElementById('btnText');
|
||||||
|
const btnLoader = document.getElementById('btnLoader');
|
||||||
|
const statusDiv = document.getElementById('resetStatus');
|
||||||
|
|
||||||
|
if (!confirm('Are you sure you want to restart the bot process?')) return;
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btnText.classList.add('d-none');
|
||||||
|
btnLoader.classList.remove('d-none');
|
||||||
|
statusDiv.classList.add('d-none');
|
||||||
|
|
||||||
|
fetch('api/reset_bot.php', {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
btn.disabled = false;
|
||||||
|
btnText.classList.remove('d-none');
|
||||||
|
btnLoader.classList.add('d-none');
|
||||||
|
statusDiv.classList.remove('d-none');
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
statusDiv.className = 'hint mt-2 text-center text-success';
|
||||||
|
statusDiv.textContent = data.message + '. Refreshing page...';
|
||||||
|
setTimeout(() => location.reload(), 2000);
|
||||||
|
} else {
|
||||||
|
statusDiv.className = 'hint mt-2 text-center text-danger';
|
||||||
|
statusDiv.textContent = 'Error: ' + (data.error || 'Unknown error');
|
||||||
|
console.error(data.details);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
btn.disabled = false;
|
||||||
|
btnText.classList.remove('d-none');
|
||||||
|
btnLoader.classList.add('d-none');
|
||||||
|
statusDiv.classList.remove('d-none');
|
||||||
|
statusDiv.className = 'hint mt-2 text-center text-danger';
|
||||||
|
statusDiv.textContent = 'Network error occurred.';
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
1104
package-lock.json
generated
1104
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,10 +4,12 @@
|
|||||||
"description": "Discord Alarm Sahur Bot",
|
"description": "Discord Alarm Sahur Bot",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/voice": "^0.17.0",
|
"@discordjs/opus": "^0.10.0",
|
||||||
|
"@discordjs/voice": "^0.19.0",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
"ffmpeg-static": "^5.3.0",
|
"ffmpeg-static": "^5.3.0",
|
||||||
"libsodium-wrappers": "^0.7.16",
|
"libsodium-wrappers": "^0.7.16",
|
||||||
"opusscript": "^0.0.8"
|
"opusscript": "^0.1.1",
|
||||||
|
"play-dl": "^1.9.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user