import { promises as fs } from 'fs' import { join } from 'path' import { createReadStream } from 'fs' export default defineEventHandler(async (event) => { let filename = getRouterParam(event, 'filename') if (!filename) { throw createError({ statusCode: 400, statusMessage: 'Filename is required' }) } // Log incoming request for debugging proxy issues const headers = getHeaders(event) const realIP = headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown' console.log(`[MUSIC API] Request from ${realIP} for file: ${filename}`) console.log('Original filename bytes:', [...filename].map(c => c.charCodeAt(0))) // Decode the filename try { filename = decodeURIComponent(filename) console.log('Decoded filename:', filename) console.log('Decoded filename bytes:', [...filename].map(c => c.charCodeAt(0))) } catch (error) { console.error('Error decoding filename:', error) // If decoding fails, use original filename } try { const config = useRuntimeConfig() const defaultPublicPath = config.public?.musicPath || '/music' const publicRel = defaultPublicPath.replace(/^\//, '') const musicDir = process.env.MUSIC_DIR || join(process.cwd(), 'public', publicRel) const filePath = join(musicDir, filename) // Security check: ensure the file is within the music directory if (!filePath.startsWith(musicDir)) { throw createError({ statusCode: 403, statusMessage: 'Access denied' }) } // Check if file exists try { await fs.access(filePath) console.log('File found successfully:', filePath) } catch (error) { console.log('File NOT found:', filePath) console.log('Directory contents:') try { const files = await fs.readdir(musicDir) files.forEach(file => console.log(' -', file)) } catch (e) { console.log('Cannot read directory:', e) } throw createError({ statusCode: 404, statusMessage: 'File not found' }) } // Get file stats const stats = await fs.stat(filePath) // Set appropriate headers const headers = getHeaders(event) // Handle range requests for audio streaming const range = headers.range if (range) { const parts = range.replace(/bytes=/, "").split("-") const start = parseInt(parts[0], 10) const end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1 const chunksize = (end - start) + 1 setHeader(event, 'Content-Range', `bytes ${start}-${end}/${stats.size}`) setHeader(event, 'Accept-Ranges', 'bytes') setHeader(event, 'Content-Length', chunksize.toString()) setResponseStatus(event, 206) return sendStream(event, createReadStream(filePath, { start, end })) } else { // Send entire file setHeader(event, 'Content-Length', stats.size.toString()) setHeader(event, 'Accept-Ranges', 'bytes') // Set content type based on file extension const ext = filename.toLowerCase().split('.').pop() const contentTypes = { 'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'flac': 'audio/flac', 'm4a': 'audio/mp4', 'ogg': 'audio/ogg', 'aac': 'audio/aac' } setHeader(event, 'Content-Type', contentTypes[ext] || 'audio/mpeg') return sendStream(event, createReadStream(filePath)) } } catch (error) { if (error.statusCode) { throw error } console.error('Error serving music file:', error) throw createError({ statusCode: 500, statusMessage: 'Failed to serve music file' }) } })