import path from 'path' import sqlite from 'sqlite' import Promise from 'bluebird' import express from 'express' import session from 'express-session' import redis from 'connect-redis' import http from 'http' import https from 'https' import ffmpeg from 'fluent-ffmpeg' import { user, userMiddleware } from './user' import lfmda from './lastfm' require('express-async-errors') const values = require(path.join(process.cwd(), 'values.json')) const tracksPerPage = 100 const dbPromise = Promise.resolve() .then(() => sqlite.open(path.join(process.cwd(), values.database), { Promise, cache: true })) .then(db => db.migrate()) const app = express() const port = process.env.PORT || 3000 const dev = process.env.NODE_ENV === 'development' const server = http.createServer(app) const lastfm = lfmda(dbPromise) if (dev) { const morgan = require('morgan') app.use(morgan('dev')) } app.set('trust proxy', 1) const router = express.Router() const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year', 'file'] const srchcategories = ['title', 'artist', 'album'] let SessionStore = redis(session) app.use(session({ key: values.session_key || 'Session', secret: values.session_secret || 'ch4ng3 m3!', store: new SessionStore(values.redis || { port: 6379 }), resave: false, saveUninitialized: true, cookie: { secure: !dev, maxAge: 2678400000 // 1 month } })) router.get('/tracks', userMiddleware, async (req, res) => { let page = parseInt(req.query.page) || 1 if (isNaN(page)) { page = 1 } let sort = req.query.sort if (!sort || sortfields.indexOf(sort.toLowerCase()) === -1) { sort = 'artist' } let sortdir = req.query.sortdir if (!sortdir || (sortdir !== 'desc' && sortdir !== 'asc')) { sortdir = 'asc' } let db = await dbPromise let count = (await db.get('SELECT COUNT(*) FROM Track'))['COUNT(*)'] let pageCount = Math.ceil(count / tracksPerPage) if (page > pageCount) page = pageCount let offset = (page - 1) * tracksPerPage let tracks = await db.all(`SELECT * FROM Track ORDER BY ${sort} ${sortdir.toUpperCase()} LIMIT ? OFFSET ?`, tracksPerPage, offset) for (let i in tracks) { delete tracks[i].file } res.jsonp({ page, count, pageCount, tracks }) }) router.get('/tracks/search', userMiddleware, async (req, res) => { let query = req.query.q let streamable = (req.query.streamable === '1') let qr = '' let exact = false if (query.indexOf(':') !== -1) { let ctr = query.split(':') if (srchcategories.indexOf(ctr[0]) !== -1) { qr = `ifnull(${ctr[0]}, '')` query = query.substring(ctr[0].length + 1) } } if (qr === '') { for (let c in srchcategories) { let cat = srchcategories[c] if (parseInt(c) !== 0) qr += ' || ' qr += `ifnull(${cat}, '')` } } if (query.indexOf('=') !== -1 && query.indexOf('\\=') === -1) { query = query.replace('=', '') exact = true } let original = String(query) if (!exact) query = `%${query}%` let sort = req.query.sort if (!sort || sortfields.indexOf(sort.toLowerCase()) === -1) { sort = 'artist' } let sortdir = req.query.sortdir if (!sortdir || (sortdir !== 'desc' && sortdir !== 'asc')) { sortdir = 'asc' } // Paging let page = parseInt(req.query.page) || 1 if (isNaN(page)) { page = 1 } let db = await dbPromise let count = (await db.get(`SELECT COUNT(*) FROM Track WHERE ${qr} LIKE ?`, query))['COUNT(*)'] let pageCount = Math.ceil(count / tracksPerPage) if (page > pageCount) page = pageCount let offset = (page - 1) * tracksPerPage let tracks = await db.all(`SELECT * FROM Track WHERE ${qr} LIKE ? ORDER BY ${sort} ${sortdir.toUpperCase()} LIMIT ? OFFSET ?`, query, tracksPerPage, offset) let llimit = tracksPerPage - count if (streamable && page === pageCount && llimit > 1) { if (llimit < 10) llimit = 10 try { let lfm = await lastfm.search(original, llimit) if (lfm && lfm.length) { tracks = tracks.concat(lfm) count = count + lfm.length if (page == 0) page = 1 if (pageCount == 0) pageCount = 1 } } catch (e) {} } for (let i in tracks) { delete tracks[i].file } res.jsonp({ page, count, pageCount, tracks }) }) router.get('/track/:id', userMiddleware, async (req, res, next) => { let id = req.params.id let db = await dbPromise let track = await db.get('SELECT * FROM Track WHERE id = ?', id) if (!track) { track = await lastfm.getTrackMetaReal(id) if (!track) return next(new Error('404 file not found')) } delete track.file res.jsonp(track) }) router.get('/playlists', userMiddleware, async (req, res, next) => { let db = await dbPromise let playlists = await db.all('SELECT * FROM Playlist WHERE userId = ? OR userId = NULL', req.session.user) res.jsonp(playlists) }) router.get('/playlist/:id', userMiddleware, async (req, res, next) => { let id = req.params.id let db = await dbPromise let playlist = await db.get('SELECT title FROM Playlist WHERE id = ?', id) if (!playlist) return next(new Error('404 file not found')) let tracks = await db.all('SELECT trackId FROM PlaylistEntry WHERE playlistId = ?', id) playlist.tracks = tracks res.jsonp(playlist) }) router.get('/serve/by-id/:id', userMiddleware, async (req, res, next) => { let id = req.params.id let dl = (req.query.dl === '1') let db = await dbPromise let track = await db.get('SELECT file FROM Track WHERE id = ?', id) if (!track) { track = await lastfm.getTrackMetaReal(id) if (!track) return next(new Error('404 file not found')) if (dl) { lastfm.invokeDownload(id) return res.end('

OK

') } dev && console.log("Remote", track.file) return ffmpeg(track.file) .audioCodec('libmp3lame') .format('mp3') .on('error', (e) => console.error(e)) .pipe(res, {end: true}) } let fpath = path.resolve(track.file) res.set('Cache-Control', 'public, max-age=31557600') if (dl) return res.download(fpath) res.redirect('/file/track' + fpath.substring(values.directory.length)) }) router.use((err, req, res, next) => { console.error(err) res.status(404).jsonp({error: 404}) }) app.use('/user', user(dbPromise, values.oauth, values.registrations === true)) app.use('/api', router) app.use('/file/track', express.static(path.resolve(values.directory))) app.use('/', express.static(path.join(process.cwd(), 'public'))) const host = process.env.NODE_ENV === 'development' ? '0.0.0.0' : '127.0.0.1' server.listen(port, host, function () { console.log(`app running on port ${port}`) })