btrtracks/src/server.js

247 lines
6.4 KiB
JavaScript

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'))
}
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: process.env.NODE_ENV !== 'development',
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')
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('<p>OK</p><script>window.close();</script>')
}
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}`)
})