diff --git a/api/save_settings.php b/api/save_settings.php new file mode 100644 index 0000000..2e4c890 --- /dev/null +++ b/api/save_settings.php @@ -0,0 +1,27 @@ + false, 'error' => 'Invalid method']); + exit; +} + +$data = [ + 'discord_token' => $_POST['discord_token'] ?? '', + 'voice_channel_id' => $_POST['voice_channel_id'] ?? '', + 'sahur_time' => $_POST['sahur_time'] ?? '03:30', +]; + +try { + $db = db(); + foreach ($data as $key => $value) { + $stmt = $db->prepare('INSERT INTO bot_settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = ?'); + $stmt->execute([$key, $value, $value]); + } + echo json_encode(['success' => true]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/assets/audio/sahur.mp3 b/assets/audio/sahur.mp3 new file mode 100644 index 0000000..e69de29 diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..c4a9ada --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,126 @@ +:root { + --primary: #10b981; + --primary-hover: #059669; + --bg: #f8fafc; + --surface: #ffffff; + --text: #1e293b; + --text-muted: #64748b; + --border: #e2e8f0; + --radius: 4px; +} + +body { + background-color: var(--bg); + color: var(--text); + font-family: 'Inter', system-ui, -apple-system, sans-serif; + line-height: 1.5; + margin: 0; +} + +.container { + max-width: 800px; + margin: 40px auto; + padding: 0 20px; +} + +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 24px; + margin-bottom: 24px; +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; + margin: 0 0 16px 0; + display: flex; + align-items: center; + gap: 8px; +} + +.form-group { + margin-bottom: 16px; +} + +.form-label { + display: block; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 4px; + color: var(--text-muted); +} + +.form-control { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: var(--radius); + font-size: 0.875rem; + transition: border-color 0.2s; + box-sizing: border-box; +} + +.form-control:focus { + outline: none; + border-color: var(--primary); +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border-radius: var(--radius); + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: 1px solid transparent; +} + +.btn-primary { + background: var(--primary); + color: white; +} + +.btn-primary:hover { + background: var(--primary-hover); +} + +.badge { + display: inline-flex; + padding: 2px 8px; + font-size: 0.75rem; + font-weight: 600; + border-radius: 9999px; + text-transform: uppercase; +} + +.badge-disconnected { + background: #fee2e2; + color: #991b1b; +} + +.badge-connected { + background: #dcfce7; + color: #166534; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 32px; +} + +.header h1 { + font-size: 1.5rem; + margin: 0; +} + +.audio-player { + width: 100%; + margin-top: 8px; +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..832d6bd --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,54 @@ +document.addEventListener('DOMContentLoaded', () => { + const configForm = document.getElementById('config-form'); + const testAudioBtn = document.getElementById('test-audio-btn'); + const audioPlayer = document.querySelector('.audio-player'); + + if (configForm) { + configForm.addEventListener('submit', async (e) => { + e.preventDefault(); + + const formData = new FormData(configForm); + const submitBtn = configForm.querySelector('button[type="submit"]'); + const originalBtnText = submitBtn.textContent; + + submitBtn.disabled = true; + submitBtn.textContent = 'Saving...'; + + try { + const response = await fetch('api/save_settings.php', { + method: 'POST', + body: formData + }); + + const result = await response.json(); + + if (result.success) { + alert('Settings saved successfully!'); + } else { + alert('Error: ' + result.error); + } + } catch (error) { + alert('Network error while saving settings.'); + } finally { + submitBtn.disabled = false; + submitBtn.textContent = originalBtnText; + } + }); + } + + if (testAudioBtn && audioPlayer) { + testAudioBtn.addEventListener('click', () => { + if (audioPlayer.paused) { + audioPlayer.play(); + testAudioBtn.textContent = 'Pause'; + } else { + audioPlayer.pause(); + testAudioBtn.textContent = 'Test Play Locally'; + } + }); + + audioPlayer.addEventListener('ended', () => { + testAudioBtn.textContent = 'Test Play Locally'; + }); + } +}); diff --git a/bot/index.js b/bot/index.js new file mode 100644 index 0000000..42ad0e6 --- /dev/null +++ b/bot/index.js @@ -0,0 +1,50 @@ +const { Client, GatewayIntentBits, Collection } = require('discord.js'); +const { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus } = require('@discordjs/voice'); +const { CronJob } = require('cron'); +const path = require('path'); + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildVoiceStates, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildPresences + ] +}); + +// Settings from DB or Env +const VC_ID = process.env.VC_ID || '1457687430189682781'; +const AUDIO_PATH = path.join(__dirname, '../assets/audio/sahur.mp3'); + +client.once('ready', () => { + console.log('Bot is ready!'); + + // Sahur Alarm: 03:30 Asia/Jakarta + new CronJob('30 03 * * *', async () => { + const guild = client.guilds.cache.first(); // Simplified for single server + const channel = guild.channels.cache.get(VC_ID); + + if (channel) { + const connection = joinVoiceChannel({ + channelId: channel.id, + guildId: guild.id, + adapterCreator: guild.voiceAdapterCreator, + }); + + const player = createAudioPlayer(); + const resource = createAudioResource(AUDIO_PATH); + + player.play(resource); + connection.subscribe(player); + + player.on(AudioPlayerStatus.Idle, () => { + connection.destroy(); + }); + } + }, null, true, 'Asia/Jakarta'); +}); + +client.login(process.env.DISCORD_TOKEN); + diff --git a/bot/package.json b/bot/package.json new file mode 100644 index 0000000..9b141be --- /dev/null +++ b/bot/package.json @@ -0,0 +1,13 @@ +{ + "name": "sahur-bot", + "version": "1.0.0", + "description": "Discord Sahur Bot", + "main": "index.js", + "dependencies": { + "discord.js": "^14.11.0", + "cron": "^2.3.1", + "@discordjs/voice": "^0.16.0", + "libsodium-wrappers": "^0.7.10", + "ffmpeg-static": "^5.1.0" + } +} diff --git a/db/migrations/001_create_bot_settings.sql b/db/migrations/001_create_bot_settings.sql new file mode 100644 index 0000000..42c87b3 --- /dev/null +++ b/db/migrations/001_create_bot_settings.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS bot_settings ( + setting_key VARCHAR(255) PRIMARY KEY, + setting_value TEXT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); diff --git a/index.php b/index.php index 7205f3d..c77799d 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,97 @@ query("SELECT setting_key, setting_value FROM bot_settings"); + while ($row = $stmt->fetch()) { + $settings[$row['setting_key']] = $row['setting_value']; + } +} catch (PDOException $e) { + // Table might not exist yet or other DB error +} + +$discordToken = $settings['discord_token'] ?? ''; +$voiceChannelId = $settings['voice_channel_id'] ?? '1457687430189682781'; +$sahurTime = $settings['sahur_time'] ?? '03:30'; + +$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Discord Bot for Sahur Alarm and Music'; +$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; ?> - New Style - - - - - - - - - - - - - - - + Sahur Bot Command Center + - - + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… +
+
+
+

Sahur Bot

+

Command Center

-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+
+ Offline +
+
+ +
+

Bot Configuration

+
+
+ + + Never share your token with anyone. +
+
+ + +
+
+ + +
+ +
-
- + +
+

Sound Preview

+

This sound will be played in the voice channel at WIB.

+ +
+ +
+
+ +
+

Bot Code Status

+

The discord.js v14 bot skeleton has been generated in the /bot directory.

+
+ # To start the bot:
+ cd bot
+ npm install
+ DISCORD_TOKEN="your_token" node index.js +
+
+ + + + +