megabot/index.html
megaproxy a3ed25f8dd Initial commit: Enhanced IRC bot with duck hunt game
- Added hunt/feed duck mechanics (80% hunt, 20% feed)
- Implemented persistent scoring system
- Added channel control commands (\!stopducks/\!startducks)
- Enhanced duck hunt with wrong action penalties
- Organized bot structure with botmain.js as main file
- Added comprehensive documentation (README.md)
- Included 17 plugins with various games and utilities

🦆 Duck Hunt Features:
- Hunt ducks with \!shoot/\!bang (80% of spawns)
- Feed ducks with \!feed (20% of spawns)
- Persistent scores saved to JSON
- Channel-specific controls for #bakedbeans
- Reaction time tracking and special achievements

🎮 Other Games:
- Casino games (slots, coinflip, hi-lo, scratch cards)
- Multiplayer games (pigs, zombie dice, quiplash)
- Text generation (babble, conspiracy, drunk historian)
- Interactive features (story writing, emojify, combos)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-17 19:03:45 +00:00

509 lines
No EOL
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Music Player</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
color: white;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.container {
max-width: 800px;
width: 100%;
background: rgba(0, 0, 0, 0.8);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.1);
}
h1 {
font-size: 3em;
margin-bottom: 10px;
text-align: center;
background: linear-gradient(45deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
text-align: center;
margin-bottom: 40px;
opacity: 0.8;
font-size: 1.1em;
}
.player-container {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
text-align: center;
}
.track-info {
margin-bottom: 30px;
}
.track-title {
font-size: 2.2em;
margin-bottom: 10px;
color: #4ecdc4;
word-break: break-word;
}
.track-details {
font-size: 1.1em;
opacity: 0.8;
margin-bottom: 5px;
}
.audio-player {
width: 100%;
margin: 20px 0;
border-radius: 10px;
background: rgba(0, 0, 0, 0.5);
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin: 30px 0;
flex-wrap: wrap;
}
.control-btn {
background: linear-gradient(45deg, #4ecdc4, #44a08d);
color: white;
padding: 15px 25px;
border: none;
border-radius: 25px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1em;
min-width: 120px;
}
.control-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
background: linear-gradient(45deg, #44a08d, #4ecdc4);
}
.control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.status {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
text-align: center;
}
.status.loading {
color: #ffd700;
}
.status.error {
color: #ff6b6b;
}
.status.success {
color: #4ecdc4;
}
.playlist {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
margin-top: 30px;
max-height: 400px;
overflow-y: auto;
}
.playlist h3 {
color: #4ecdc4;
margin-bottom: 15px;
text-align: center;
}
.track-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
.track-item:hover {
background: rgba(255, 255, 255, 0.2);
border-left-color: #4ecdc4;
}
.track-item.current {
background: rgba(78, 205, 196, 0.3);
border-left-color: #4ecdc4;
}
.visualizer {
height: 60px;
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
margin: 20px 0;
display: flex;
align-items: end;
justify-content: center;
gap: 2px;
padding: 10px;
}
.bar {
width: 4px;
background: linear-gradient(to top, #4ecdc4, #667eea);
border-radius: 2px;
transition: height 0.1s ease;
height: 5px;
}
.settings {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.settings h4 {
color: #4ecdc4;
margin-bottom: 15px;
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.setting-input {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 5px;
padding: 8px 12px;
color: white;
width: 200px;
}
.setting-input:focus {
outline: none;
border-color: #4ecdc4;
}
@media (max-width: 768px) {
.container { padding: 20px; }
h1 { font-size: 2em; }
.track-title { font-size: 1.5em; }
.controls { flex-direction: column; align-items: center; }
.control-btn { width: 200px; }
.setting-item { flex-direction: column; gap: 10px; }
.setting-input { width: 100%; }
}
</style>
</head>
<body>
<div class="container">
<h1>🎵 Server Music Player</h1>
<p class="subtitle">Random FLAC player from server directory</p>
<div class="settings">
<h4>⚙️ Server Configuration</h4>
<div class="setting-item">
<label>Server URL:</label>
<input type="text" id="serverUrl" class="setting-input" value="http://localhost:3000" placeholder="http://localhost:3000">
</div>
<div class="setting-item">
<label>Music Directory:</label>
<input type="text" id="musicDir" class="setting-input" value="/music" placeholder="/path/to/music">
</div>
<button class="control-btn" onclick="connectToServer()">🔗 Connect</button>
</div>
<div class="status" id="status">
Click "Connect" to start browsing your music library
</div>
<div class="player-container" id="playerContainer" style="display: none;">
<div class="track-info">
<div class="track-title" id="trackTitle">No track loaded</div>
<div class="track-details" id="trackPath">Path: N/A</div>
<div class="track-details" id="trackSize">Size: N/A</div>
<div class="track-details" id="trackFormat">Format: FLAC</div>
</div>
<div class="visualizer" id="visualizer"></div>
<audio id="audioPlayer" class="audio-player" controls>
Your browser does not support the audio element.
</audio>
<div class="controls">
<button class="control-btn" onclick="previousTrack()">⏮️ Previous</button>
<button class="control-btn" onclick="getRandomTrack()">🎲 Random</button>
<button class="control-btn" onclick="nextTrack()">⏭️ Next</button>
<button class="control-btn" onclick="refreshLibrary()">🔄 Refresh</button>
</div>
</div>
<div class="playlist" id="playlist" style="display: none;">
<h3>🎵 Music Library</h3>
<div id="trackList"></div>
</div>
</div>
<script>
let musicLibrary = [];
let currentTrackIndex = 0;
let audioPlayer = null;
let serverUrl = 'http://localhost:3000';
let musicDirectory = '/music';
function updateStatus(message, type = 'info') {
const status = document.getElementById('status');
status.textContent = message;
status.className = `status ${type}`;
}
async function connectToServer() {
serverUrl = document.getElementById('serverUrl').value;
musicDirectory = document.getElementById('musicDir').value;
updateStatus('Connecting to server...', 'loading');
try {
const response = await fetch(`${serverUrl}/api/tracks?dir=${encodeURIComponent(musicDirectory)}`);
if (!response.ok) {
throw new Error(`Server responded with ${response.status}`);
}
musicLibrary = await response.json();
if (musicLibrary.length === 0) {
updateStatus('No FLAC files found in the specified directory', 'error');
return;
}
updateStatus(`Connected! Found ${musicLibrary.length} tracks`, 'success');
document.getElementById('playerContainer').style.display = 'block';
document.getElementById('playlist').style.display = 'block';
setupPlayer();
generatePlaylist();
getRandomTrack();
} catch (error) {
updateStatus(`Connection failed: ${error.message}`, 'error');
console.error('Connection error:', error);
}
}
function setupPlayer() {
audioPlayer = document.getElementById('audioPlayer');
audioPlayer.addEventListener('loadstart', () => {
document.getElementById('trackTitle').textContent = 'Loading...';
});
audioPlayer.addEventListener('loadedmetadata', () => {
updateTrackInfo();
animateVisualizer();
});
audioPlayer.addEventListener('ended', () => {
nextTrack();
});
audioPlayer.addEventListener('play', () => {
animateVisualizer();
});
audioPlayer.addEventListener('pause', () => {
stopVisualizer();
});
createVisualizer();
}
function generatePlaylist() {
const trackList = document.getElementById('trackList');
trackList.innerHTML = '';
musicLibrary.forEach((track, index) => {
const trackItem = document.createElement('div');
trackItem.className = 'track-item';
trackItem.onclick = () => playTrack(index);
trackItem.innerHTML = `
<div><strong>${track.name}</strong></div>
<div style="font-size: 0.9em; opacity: 0.7;">${track.path}</div>
<div style="font-size: 0.8em; opacity: 0.6;">Size: ${(track.size / (1024 * 1024)).toFixed(1)} MB</div>
`;
trackList.appendChild(trackItem);
});
}
async function playTrack(index) {
if (index < 0 || index >= musicLibrary.length) return;
currentTrackIndex = index;
const track = musicLibrary[index];
updateStatus('Loading track...', 'loading');
try {
// Get the audio stream URL from server
const streamUrl = `${serverUrl}/api/stream/${encodeURIComponent(track.path)}`;
audioPlayer.src = streamUrl;
updateTrackInfo();
updatePlaylistHighlight();
updateStatus('Track loaded successfully', 'success');
} catch (error) {
updateStatus(`Failed to load track: ${error.message}`, 'error');
}
}
function updateTrackInfo() {
const track = musicLibrary[currentTrackIndex];
document.getElementById('trackTitle').textContent = track.name;
document.getElementById('trackPath').textContent = `Path: ${track.path}`;
document.getElementById('trackSize').textContent = `Size: ${(track.size / (1024 * 1024)).toFixed(1)} MB`;
document.getElementById('trackFormat').textContent = 'Format: FLAC';
}
function updatePlaylistHighlight() {
const trackItems = document.querySelectorAll('.track-item');
trackItems.forEach((item, index) => {
item.classList.toggle('current', index === currentTrackIndex);
});
}
function getRandomTrack() {
if (musicLibrary.length === 0) return;
let randomIndex;
do {
randomIndex = Math.floor(Math.random() * musicLibrary.length);
} while (randomIndex === currentTrackIndex && musicLibrary.length > 1);
playTrack(randomIndex);
}
function nextTrack() {
if (musicLibrary.length === 0) return;
const nextIndex = (currentTrackIndex + 1) % musicLibrary.length;
playTrack(nextIndex);
}
function previousTrack() {
if (musicLibrary.length === 0) return;
const prevIndex = currentTrackIndex > 0 ? currentTrackIndex - 1 : musicLibrary.length - 1;
playTrack(prevIndex);
}
function refreshLibrary() {
connectToServer();
}
function createVisualizer() {
const visualizer = document.getElementById('visualizer');
visualizer.innerHTML = '';
for (let i = 0; i < 50; i++) {
const bar = document.createElement('div');
bar.className = 'bar';
visualizer.appendChild(bar);
}
}
function animateVisualizer() {
const bars = document.querySelectorAll('.bar');
function animate() {
if (audioPlayer && !audioPlayer.paused) {
bars.forEach((bar, index) => {
const height = Math.random() * 40 + 5;
bar.style.height = height + 'px';
bar.style.animationDelay = `${index * 0.05}s`;
});
setTimeout(() => requestAnimationFrame(animate), 100);
}
}
animate();
}
function stopVisualizer() {
const bars = document.querySelectorAll('.bar');
bars.forEach(bar => {
bar.style.height = '5px';
});
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.tagName !== 'INPUT') {
switch(e.code) {
case 'Space':
e.preventDefault();
if (audioPlayer && audioPlayer.src) {
if (audioPlayer.paused) {
audioPlayer.play();
} else {
audioPlayer.pause();
}
}
break;
case 'ArrowRight':
e.preventDefault();
nextTrack();
break;
case 'ArrowLeft':
e.preventDefault();
previousTrack();
break;
case 'KeyR':
e.preventDefault();
getRandomTrack();
break;
}
}
});
</script>
</body>
</html>