diff --git a/components/dashboard/ChatDetail.tsx b/components/dashboard/ChatDetail.tsx index a02e3f0..67e6ec7 100644 --- a/components/dashboard/ChatDetail.tsx +++ b/components/dashboard/ChatDetail.tsx @@ -130,9 +130,9 @@ export default function ChatDetail({ chatId }: { chatId: string }) { // Initialize audio element useEffect(() => { // Create audio element for notification sound - audioRef.current = new Audio('/notification.mp3'); + audioRef.current = new Audio('/hohoho.mp3'); - // Fallback if notification.mp3 doesn't exist - use browser API for a simple beep + // Fallback if hohoho.mp3 doesn't exist - use browser API for a simple beep audioRef.current.addEventListener('error', () => { audioRef.current = null; }); diff --git a/components/dashboard/ChatTable.tsx b/components/dashboard/ChatTable.tsx index 0d7b16e..d3b981b 100644 --- a/components/dashboard/ChatTable.tsx +++ b/components/dashboard/ChatTable.tsx @@ -81,7 +81,7 @@ export default function ChatTable() { // Initialize audio element for notifications useEffect(() => { - audioRef.current = new Audio('/notification.mp3'); + audioRef.current = new Audio('/hohoho.mp3'); return () => { if (audioRef.current) { audioRef.current = null; diff --git a/components/notifications/OrderNotifications.tsx b/components/notifications/OrderNotifications.tsx index 64c3022..b1eae7f 100644 --- a/components/notifications/OrderNotifications.tsx +++ b/components/notifications/OrderNotifications.tsx @@ -33,9 +33,9 @@ export default function OrderNotifications() { const audioRef = useRef(null); useEffect(() => { - audioRef.current = new Audio('/notification.mp3'); + audioRef.current = new Audio('/hohoho.mp3'); - // Fallback if notification.mp3 doesn't exist + // Fallback if hohoho.mp3 doesn't exist audioRef.current.addEventListener('error', () => { audioRef.current = null; }); diff --git a/lib/notification-context.tsx b/lib/notification-context.tsx index c161c44..daeb11e 100644 --- a/lib/notification-context.tsx +++ b/lib/notification-context.tsx @@ -92,7 +92,7 @@ export function NotificationProvider({ children }: NotificationProviderProps) { } // Initialize audio - audioRef.current = new Audio('/notification.mp3'); + audioRef.current = new Audio('/hohoho.mp3'); audioRef.current.addEventListener('error', () => { audioRef.current = null; }); diff --git a/package-lock.json b/package-lock.json index 323a7c4..d4a1e69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "zod": "^3.24.1" }, "devDependencies": { + "@distube/ytdl-core": "^4.16.12", "@next/bundle-analyzer": "^15.4.6", "@tailwindcss/typography": "^0.5.16", "@types/lodash": "^4.17.16", @@ -112,6 +113,28 @@ "node": ">=10.0.0" } }, + "node_modules/@distube/ytdl-core": { + "version": "4.16.12", + "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.12.tgz", + "integrity": "sha512-/NR8Jur1Q4E2oD+DJta7uwWu7SkqdEkhwERt7f4iune70zg7ZlLLTOHs1+jgg3uD2jQjpdk7RGC16FqstG4RsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-cookie-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "m3u8stream": "^0.8.6", + "miniget": "^4.2.3", + "sax": "^1.4.1", + "tough-cookie": "^5.1.2", + "undici": "^7.8.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/distubejs/ytdl-core?sponsor" + } + }, "node_modules/@emnapi/core": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", @@ -3041,6 +3064,16 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5603,6 +5636,45 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/http-cookie-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-7.0.3.tgz", + "integrity": "sha512-EeZo7CGhfqPW6R006rJa4QtZZUpBygDa2HZH3DJqsTzTjyRE6foDBVQIv/pjVsxHC8z2GIdbB1Hvn9SRorP3WQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/3846masa" + }, + "peerDependencies": { + "tough-cookie": "^4.0.0 || ^5.0.0 || ^6.0.0", + "undici": "^7.0.0" + }, + "peerDependenciesMeta": { + "undici": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6448,6 +6520,20 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/m3u8stream": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz", + "integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "miniget": "^4.2.2", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7097,6 +7183,16 @@ "node": ">= 0.6" } }, + "node_modules/miniget": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz", + "integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8398,6 +8494,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/scheduler": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", @@ -9131,6 +9234,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -9154,6 +9277,19 @@ "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -9337,6 +9473,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", diff --git a/package.json b/package.json index 8c217c2..cef21e7 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "zod": "^3.24.1" }, "devDependencies": { + "@distube/ytdl-core": "^4.16.12", "@next/bundle-analyzer": "^15.4.6", "@tailwindcss/typography": "^0.5.16", "@types/lodash": "^4.17.16", diff --git a/public/hohoho.mp3 b/public/hohoho.mp3 new file mode 100644 index 0000000..4921a6f Binary files /dev/null and b/public/hohoho.mp3 differ diff --git a/scripts/download-notification-sound.js b/scripts/download-notification-sound.js new file mode 100644 index 0000000..b615055 --- /dev/null +++ b/scripts/download-notification-sound.js @@ -0,0 +1,74 @@ +/** + * Script to download notification sound from YouTube + * Uses @distube/ytdl-core to download audio + */ + +const ytdl = require('@distube/ytdl-core'); +const fs = require('fs'); +const path = require('path'); + +const YOUTUBE_URL = 'https://www.youtube.com/watch?v=utmKQWAuYH4'; +const OUTPUT_PATH = path.join(__dirname, '..', 'public', 'notification.mp3'); + +console.log('Downloading notification sound from YouTube...'); +console.log(`URL: ${YOUTUBE_URL}`); +console.log(`Output: ${OUTPUT_PATH}`); + +async function downloadAudio() { + try { + // Validate URL + if (!ytdl.validateURL(YOUTUBE_URL)) { + throw new Error('Invalid YouTube URL'); + } + + // Get video info + console.log('\nFetching video information...'); + const info = await ytdl.getInfo(YOUTUBE_URL); + console.log(`Title: ${info.videoDetails.title}`); + + // Get audio format + const audioFormats = ytdl.filterFormats(info.formats, 'audioonly'); + if (audioFormats.length === 0) { + throw new Error('No audio formats available'); + } + + // Select best quality audio + const format = audioFormats[0]; + console.log(`\nDownloading audio format: ${format.audioCodec || 'unknown'}`); + + // Create write stream + const outputStream = fs.createWriteStream(OUTPUT_PATH); + + // Download audio + const audioStream = ytdl.downloadFromInfo(info, { format }); + + audioStream.pipe(outputStream); + + audioStream.on('progress', (chunkLength, downloaded, total) => { + const percent = (downloaded / total * 100).toFixed(2); + process.stdout.write(`\rDownloaded: ${percent}%`); + }); + + await new Promise((resolve, reject) => { + outputStream.on('finish', () => { + console.log('\n\n✅ Successfully downloaded notification sound!'); + resolve(); + }); + outputStream.on('error', reject); + audioStream.on('error', reject); + }); + + } catch (error) { + console.error('\n❌ Error downloading audio:', error.message); + console.error('\nAlternative options:'); + console.log('1. Install yt-dlp: pip install yt-dlp'); + console.log('2. Download executable: https://github.com/yt-dlp/yt-dlp/releases'); + console.log('3. Use online converter'); + console.log('\nThen run:'); + console.log(`yt-dlp -x --audio-format mp3 --audio-quality 0 -o "public/notification.mp3" "${YOUTUBE_URL}"`); + process.exit(1); + } +} + +downloadAudio(); + diff --git a/yt-dlp.exe b/yt-dlp.exe new file mode 100644 index 0000000..ac0e0d8 Binary files /dev/null and b/yt-dlp.exe differ