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>
This commit is contained in:
commit
a3ed25f8dd
39 changed files with 12360 additions and 0 deletions
509
index.html
Normal file
509
index.html
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
<!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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue