listen by youtube url, fix context menu positioning
This commit is contained in:
parent
7a3ad95d60
commit
710f1a5e48
@ -170,11 +170,11 @@
|
||||
return false
|
||||
}
|
||||
|
||||
function getPosition(e) {
|
||||
function getPosition (e) {
|
||||
var posx = 0
|
||||
var posy = 0
|
||||
|
||||
if (!e) var e = window.event
|
||||
if (!e) e = window.event
|
||||
|
||||
if (e.pageX || e.pageY) {
|
||||
posx = e.pageX
|
||||
@ -192,25 +192,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
function positionMenu(e) {
|
||||
clickCoords = getPosition(e)
|
||||
clickCoordsX = clickCoords.x
|
||||
clickCoordsY = clickCoords.y
|
||||
function positionMenu (e) {
|
||||
const clickCoords = getPosition(e)
|
||||
const clickCoordsX = clickCoords.x
|
||||
const clickCoordsY = clickCoords.y
|
||||
|
||||
menuWidth = menu.offsetWidth + 4
|
||||
menuHeight = menu.offsetHeight + 4
|
||||
const menuWidth = menu.offsetWidth + 4
|
||||
const menuHeight = menu.offsetHeight + 4
|
||||
|
||||
windowWidth = window.innerWidth
|
||||
windowHeight = window.innerHeight
|
||||
const windowWidth = window.innerWidth
|
||||
const windowHeight = window.innerHeight - 80
|
||||
|
||||
if ( (windowWidth - clickCoordsX) < menuWidth ) {
|
||||
menu.style.left = windowWidth - menuWidth + 'px'
|
||||
if ((windowWidth - clickCoordsX) < menuWidth) {
|
||||
menu.style.left = (windowWidth - menuWidth) + 'px'
|
||||
} else {
|
||||
menu.style.left = clickCoordsX + 'px'
|
||||
}
|
||||
|
||||
if ( (windowHeight - clickCoordsY) < menuHeight ) {
|
||||
menu.style.top = windowHeight - menuHeight + 'px'
|
||||
if ((windowHeight - clickCoordsY) < menuHeight) {
|
||||
menu.style.top = (windowHeight - menuHeight) + 'px'
|
||||
} else {
|
||||
menu.style.top = clickCoordsY + 'px'
|
||||
}
|
||||
@ -330,7 +330,6 @@
|
||||
}
|
||||
|
||||
function ctxTrack (xid, ev, qe) {
|
||||
positionMenu(ev)
|
||||
menu.style.display = 'block'
|
||||
|
||||
let qbtn = menu.querySelector('.ctx-item[data-action="queue"]')
|
||||
@ -345,6 +344,8 @@
|
||||
let plDel = menu.querySelector('.ctx-item[data-action="playlist-remove"]')
|
||||
plDel.style.display = (playlist != null && playlist >= 0) ? 'block' : 'none'
|
||||
|
||||
positionMenu(ev)
|
||||
|
||||
ctxState = xid
|
||||
}
|
||||
|
||||
@ -825,7 +826,7 @@
|
||||
ctxHide()
|
||||
}, false)
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
document.addEventListener('click', function (e) {
|
||||
var clickeElIsLink = clickInsideElement(e, 'ctx-item')
|
||||
|
||||
if (clickeElIsLink) {
|
||||
@ -833,7 +834,7 @@
|
||||
ctxHandle(clickeElIsLink)
|
||||
} else {
|
||||
var button = e.which || e.button
|
||||
if ( button === 1 ) {
|
||||
if (button === 1) {
|
||||
ctxHide()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import {spawn} from 'child_process'
|
||||
import { spawn } from 'child_process'
|
||||
import ffmpeg from 'fluent-ffmpeg'
|
||||
|
||||
import path from 'path'
|
||||
import asn from './async'
|
||||
|
||||
function parseTitle (data) {
|
||||
let tt = data.title
|
||||
@ -11,16 +10,16 @@ function parseTitle (data) {
|
||||
tt = tt.replace(/^\[\w+\]\s?/i, '')
|
||||
|
||||
// Remove "Official Video/Audio" tag
|
||||
tt = tt.replace(/\s?(?:[\(\)\[\]])?Official\s?[\w]+(?:[\(\)\[\]])?/i, '')
|
||||
tt = tt.replace(/\s?(?:[()[]])?Official\s?[\w]+(?:[()[]])?/i, '')
|
||||
|
||||
// Remove "Audio" tag
|
||||
tt = tt.replace(/\s?(?:[\(\)\[\]])Audio?(?:[\(\)\[\]])/i, '')
|
||||
tt = tt.replace(/\s?(?:[()[]])Audio?(?:[()[]])/i, '')
|
||||
|
||||
// Remove "lyrics" tag
|
||||
tt = tt.replace(/\s?(?:[\(\)\[\]])?lyrics?\s?(?:[\w]+)?(?:[\(\)\[\]])?\s?/i, '')
|
||||
tt = tt.replace(/\s?(?:[()[]])?lyrics?\s?(?:[\w]+)?(?:[()[]])?\s?/i, '')
|
||||
|
||||
// Artist / Title split
|
||||
let at = tt.split(' - ', 2)
|
||||
const at = tt.split(' - ', 2)
|
||||
let artist
|
||||
let title
|
||||
|
||||
@ -39,7 +38,7 @@ function parseTitle (data) {
|
||||
}
|
||||
|
||||
function getVideoInfo (arg) {
|
||||
let yt = spawn('youtube-dl', ['--no-playlist', '-j', '-f', 'bestaudio/best', arg])
|
||||
const yt = spawn('youtube-dl', ['--no-playlist', '-j', '-f', 'bestaudio/best', arg])
|
||||
|
||||
let output = ''
|
||||
yt.stdout.on('data', function (chunk) {
|
||||
@ -48,10 +47,10 @@ function getVideoInfo (arg) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
yt.on('close', function () {
|
||||
let ftdata = output.trim().split('\n')
|
||||
const ftdata = output.trim().split('\n')
|
||||
if (ftdata.length > 1) {
|
||||
let composite = []
|
||||
for (let i in ftdata) {
|
||||
const composite = []
|
||||
for (const i in ftdata) {
|
||||
let dat
|
||||
try {
|
||||
dat = JSON.parse(ftdata[i])
|
||||
@ -65,7 +64,7 @@ function getVideoInfo (arg) {
|
||||
return resolve(composite)
|
||||
}
|
||||
|
||||
let data = JSON.parse(output)
|
||||
const data = JSON.parse(output)
|
||||
delete data.formats
|
||||
resolve(data)
|
||||
})
|
||||
@ -74,7 +73,7 @@ function getVideoInfo (arg) {
|
||||
|
||||
function fetchVideo (data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let tempName = path.join(process.cwd(), `/tmp.yt.${data.id}.mp3`)
|
||||
const tempName = path.join(process.cwd(), `/tmp.yt.${data.id}.mp3`)
|
||||
ffmpeg(data.url || data.file)
|
||||
.audioCodec('libmp3lame')
|
||||
.format('mp3')
|
||||
|
@ -1,4 +1,3 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import readline from 'readline'
|
||||
|
||||
@ -12,33 +11,35 @@ const musicdir = path.resolve(values.directory)
|
||||
// ffprobe -i <file> -show_entries format=duration -v quiet -of csv="p=0"
|
||||
|
||||
async function interactive (fpath) {
|
||||
let rl = readline.createInterface({
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
console.log('=> No metadata found for specified file! Interactive mode enabled.\n')
|
||||
|
||||
let pt = path.parse(fpath)
|
||||
let track = {
|
||||
const pt = path.parse(fpath)
|
||||
const track = {
|
||||
file: fpath,
|
||||
title: pt.name
|
||||
}
|
||||
let clean = dl.parseTitle(track)
|
||||
const clean = dl.parseTitle(track)
|
||||
|
||||
console.log('== Determined Title: ' + clean.title)
|
||||
console.log('== Determined Artist: ' + clean.artist)
|
||||
|
||||
let newTitle = await asn.askAsync(rl, `Title [${clean.title}] ? `)
|
||||
let newArtist = await asn.askAsync(rl, `Artist [${clean.artist}] ? `)
|
||||
const newTitle = await asn.askAsync(rl, `Title [${clean.title}] ? `)
|
||||
const newArtist = await asn.askAsync(rl, `Artist [${clean.artist}] ? `)
|
||||
|
||||
if (newTitle.trim() !== '')
|
||||
if (newTitle.trim() !== '') {
|
||||
track.title = newTitle
|
||||
}
|
||||
|
||||
if (newArtist.trim() !== '')
|
||||
if (newArtist.trim() !== '') {
|
||||
track.artist = newArtist
|
||||
}
|
||||
|
||||
let len = await asn.promiseExec(`ffprobe -i "${track.file}" -show_entries format=duration -v quiet -of csv="p=0"`)
|
||||
const len = await asn.promiseExec(`ffprobe -i "${track.file}" -show_entries format=duration -v quiet -of csv="p=0"`)
|
||||
track.duration = parseFloat(len)
|
||||
|
||||
rl.close()
|
||||
@ -47,7 +48,7 @@ async function interactive (fpath) {
|
||||
}
|
||||
|
||||
async function handlePassed (db, fpath) {
|
||||
let filePath = path.resolve(fpath)
|
||||
const filePath = path.resolve(fpath)
|
||||
let trackinf
|
||||
|
||||
try {
|
||||
@ -60,18 +61,18 @@ async function handlePassed (db, fpath) {
|
||||
throw new Error('Nothing to do.')
|
||||
}
|
||||
|
||||
let ins = await asn.insertDB(db, trackinf)
|
||||
const ins = await asn.insertDB(db, trackinf)
|
||||
if (!ins) {
|
||||
throw new Error('A track of this description already exists in the database.')
|
||||
}
|
||||
}
|
||||
|
||||
async function run () {
|
||||
let db = await dbPromise
|
||||
const db = await dbPromise
|
||||
|
||||
if (process.argv[2] != null) {
|
||||
for (let i = 2; i < process.argv.length; i++) {
|
||||
let f = process.argv[i]
|
||||
const f = process.argv[i]
|
||||
console.log('=> Passing', f)
|
||||
try {
|
||||
await handlePassed(db, f)
|
||||
@ -84,16 +85,16 @@ async function run () {
|
||||
return
|
||||
}
|
||||
|
||||
let files = await asn.getFiles(musicdir)
|
||||
let cleanTrackData = []
|
||||
const files = await asn.getFiles(musicdir)
|
||||
const cleanTrackData = []
|
||||
let skips = 0
|
||||
|
||||
for (let i in files) {
|
||||
let file = files[i]
|
||||
for (const i in files) {
|
||||
const file = files[i]
|
||||
// if (cleanTrackData.length > 10) break // debug purposes
|
||||
process.stdout.write(`\rProcessing file ${parseInt(i) + 1} of ${files.length}.. (${skips} skipped)`)
|
||||
try {
|
||||
let fd = await asn.getInfos(file)
|
||||
const fd = await asn.getInfos(file)
|
||||
cleanTrackData.push(fd)
|
||||
} catch (e) {
|
||||
skips++
|
||||
@ -103,11 +104,11 @@ async function run () {
|
||||
process.stdout.write(`\r${cleanTrackData.length} files indexed, ${skips} files were skipped. \n`)
|
||||
let entries = 0
|
||||
|
||||
for (let i in cleanTrackData) {
|
||||
let track = cleanTrackData[i]
|
||||
for (const i in cleanTrackData) {
|
||||
const track = cleanTrackData[i]
|
||||
process.stdout.write(`\rPopulating database.. (Track ${parseInt(i) + 1} of ${cleanTrackData.length})`)
|
||||
try {
|
||||
let ins = await asn.insertDB(db, track)
|
||||
const ins = await asn.insertDB(db, track)
|
||||
if (!ins) continue
|
||||
entries++
|
||||
} catch (e) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
import fs from 'fs-extra'
|
||||
import readline from 'readline'
|
||||
import path from 'path'
|
||||
|
||||
@ -18,13 +17,13 @@ const rl = readline.createInterface({
|
||||
|
||||
async function download (furl) {
|
||||
console.log('=> Getting information..')
|
||||
let data = await dl.getVideoInfo(furl)
|
||||
const data = await dl.getVideoInfo(furl)
|
||||
|
||||
console.log('=> Downloading file..')
|
||||
let file = await dl.fetchVideo(data)
|
||||
const file = await dl.fetchVideo(data)
|
||||
|
||||
console.log('=> Cleaning up..')
|
||||
let clean = dl.parseTitle(file)
|
||||
const clean = dl.parseTitle(file)
|
||||
let filename = clean.artist + ' - ' + clean.title + '.mp3'
|
||||
|
||||
console.log('=> Original Title: ' + file.title + '\n')
|
||||
@ -32,9 +31,9 @@ async function download (furl) {
|
||||
console.log('== Determined Artist: ' + clean.artist)
|
||||
console.log('== Determined File Name: ' + filename)
|
||||
|
||||
let titleAnsw = await asn.askAsync(rl, `Title [${clean.title}] ? `)
|
||||
let artistAnsw = await asn.askAsync(rl, `Artist [${clean.artist}] ? `)
|
||||
let fileAnsw = await asn.askAsync(rl, `File [${filename}] ? `)
|
||||
const titleAnsw = await asn.askAsync(rl, `Title [${clean.title}] ? `)
|
||||
const artistAnsw = await asn.askAsync(rl, `Artist [${clean.artist}] ? `)
|
||||
const fileAnsw = await asn.askAsync(rl, `File [${filename}] ? `)
|
||||
|
||||
if (titleAnsw && titleAnsw.trim() !== '') {
|
||||
clean.title = titleAnsw
|
||||
@ -48,14 +47,14 @@ async function download (furl) {
|
||||
filename = fileAnsw
|
||||
}
|
||||
|
||||
let fn = path.join(musicdir, filename)
|
||||
const fn = path.join(musicdir, filename)
|
||||
await asn.setMetadata(fn, clean, file.source, true)
|
||||
|
||||
let addAnsw = await asn.askAsync(rl, `Would you like to add it to the database now? [N/y] ? `)
|
||||
const addAnsw = await asn.askAsync(rl, 'Would you like to add it to the database now? [N/y] ? ')
|
||||
if (addAnsw && addAnsw.trim().toLowerCase().indexOf('y') === 0) {
|
||||
// Add to database
|
||||
try {
|
||||
let verify = await asn.getInfos(fn)
|
||||
const verify = await asn.getInfos(fn)
|
||||
await asn.insertDB(await dbPromise, verify)
|
||||
} catch (e) {
|
||||
console.warn('=!= Add to database failed!')
|
||||
|
141
src/external.js
Normal file
141
src/external.js
Normal file
@ -0,0 +1,141 @@
|
||||
import path from 'path'
|
||||
import asn from './common/async'
|
||||
import dl from './common/download'
|
||||
import { dbPromise } from './database'
|
||||
import { URL } from 'url'
|
||||
|
||||
const fs = require('fs').promises
|
||||
const values = require(path.join(process.cwd(), 'values.json'))
|
||||
const memexpire = 1800
|
||||
|
||||
const externalTracks = {}
|
||||
const downloadQueue = []
|
||||
let downloading = false
|
||||
|
||||
async function downloadLocally (id) {
|
||||
const info = await getTrackMetaReal(id)
|
||||
if (!info) throw new Error('No track with this ID in external list.')
|
||||
const file = await dl.fetchVideo(info)
|
||||
const filename = info.artist + ' - ' + info.title + '.mp3'
|
||||
info.file = path.join(values.directory, filename)
|
||||
await asn.promiseExec(`ffmpeg -i "${file.source}" -metadata artist="${info.artist}" -metadata title="${info.title}" -codec copy "${info.file}"`)
|
||||
await fs.unlink(file.source)
|
||||
const db = await dbPromise
|
||||
const ins = await asn.insertDB(db, info)
|
||||
if (!ins) {
|
||||
throw new Error('A track of this description already exists in the database.')
|
||||
}
|
||||
}
|
||||
|
||||
async function getTrackMetaReal (id) {
|
||||
if (!id || !externalTracks[id]) return null
|
||||
const trdata = externalTracks[id]
|
||||
|
||||
// Check for expiry
|
||||
if (trdata.file && trdata.expires > Date.now()) {
|
||||
return Object.assign({}, trdata)
|
||||
}
|
||||
|
||||
const trsrch = 'ytsearch3:' + trdata.artist + ' - ' + trdata.title
|
||||
const dldata = await dl.getVideoInfo(trsrch)
|
||||
|
||||
let bestMatch
|
||||
if (dldata.length === 1) bestMatch = dldata[0]
|
||||
|
||||
let candidates = []
|
||||
for (const i in dldata) {
|
||||
const obj = dldata[i]
|
||||
const title = obj.title.toLowerCase()
|
||||
|
||||
// Skip any video with 'video' in it, but keep lyric videos
|
||||
if (title.indexOf('video') !== -1 && title.indexOf('lyric') === -1) continue
|
||||
|
||||
// If the title has 'audio' in it, it might be the best match
|
||||
if (title.indexOf('audio') !== -1) {
|
||||
bestMatch = obj
|
||||
break
|
||||
}
|
||||
|
||||
candidates.push(obj)
|
||||
}
|
||||
|
||||
if (candidates.length && !bestMatch) {
|
||||
// Sort candidates by view count
|
||||
candidates = candidates.sort(function (a, b) {
|
||||
return b.view_count - a.view_count
|
||||
})
|
||||
|
||||
// Select the one with the most views
|
||||
bestMatch = candidates[0]
|
||||
}
|
||||
|
||||
// If there were no suitable candidates, just take the first response
|
||||
if (!candidates.length && !bestMatch) bestMatch = dldata[0]
|
||||
|
||||
externalTracks[id] = {
|
||||
id: trdata.id,
|
||||
title: trdata.title,
|
||||
artist: trdata.artist,
|
||||
file: bestMatch.url,
|
||||
duration: bestMatch.duration,
|
||||
expires: Date.now() + memexpire * 1000,
|
||||
external: true
|
||||
}
|
||||
|
||||
return Object.assign({}, externalTracks[id])
|
||||
}
|
||||
|
||||
// Download thread
|
||||
let dltd = null
|
||||
function invokeDownload (add) {
|
||||
if (add) downloadQueue.push(add)
|
||||
if (dltd) return
|
||||
dltd = setTimeout(function (argument) {
|
||||
dltd = null
|
||||
if (downloading) return invokeDownload()
|
||||
if (!downloadQueue.length) return
|
||||
downloading = true
|
||||
downloadLocally(downloadQueue.shift()).then(() => {
|
||||
downloading = false
|
||||
if (downloadQueue.length) invokeDownload()
|
||||
}).catch((e) => {
|
||||
console.error(e)
|
||||
downloading = false
|
||||
if (downloadQueue.length) invokeDownload()
|
||||
})
|
||||
}, 2 * 1000)
|
||||
}
|
||||
|
||||
function addListExternal (id, obj) {
|
||||
if (externalTracks[id]) return Object.assign({}, externalTracks[id])
|
||||
externalTracks[id] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
async function searchURL (url) {
|
||||
const urlp = new URL(url)
|
||||
if (urlp.hostname.indexOf('youtube.com') !== -1 ||
|
||||
urlp.hostname.indexOf('youtu.be') !== -1) {
|
||||
const id = urlp.searchParams.get('v') || urlp.pathname.substr(1)
|
||||
const ytb = await dl.getVideoInfo(id)
|
||||
externalTracks[id] = {
|
||||
id: id,
|
||||
title: ytb.title,
|
||||
artist: ytb.artist || ytb.uploader,
|
||||
file: ytb.url,
|
||||
duration: ytb.duration,
|
||||
expires: Date.now() + memexpire * 1000,
|
||||
external: true
|
||||
}
|
||||
return [externalTracks[id]]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export default {
|
||||
downloadLocally,
|
||||
invokeDownload,
|
||||
addListExternal,
|
||||
getTrackMetaReal,
|
||||
searchURL
|
||||
}
|
161
src/lastfm.js
161
src/lastfm.js
@ -1,18 +1,11 @@
|
||||
import path from 'path'
|
||||
import rp from 'request-promise-native'
|
||||
import asn from './common/async'
|
||||
import dl from './common/download'
|
||||
import crypto from 'crypto'
|
||||
import { dbPromise } from './database'
|
||||
import { addListExternal } from './external'
|
||||
import { parseStringPromise } from 'xml2js'
|
||||
|
||||
const fs = require('fs').promises
|
||||
const values = require(path.join(process.cwd(), 'values.json'))
|
||||
const memexpire = 1800
|
||||
|
||||
let externalTracks = {}
|
||||
let downloadQueue = []
|
||||
let downloading = false
|
||||
|
||||
function createHash (data) {
|
||||
return crypto
|
||||
@ -22,79 +15,6 @@ function createHash (data) {
|
||||
.substr(0, 8)
|
||||
}
|
||||
|
||||
async function downloadLocally (id) {
|
||||
let info = await getTrackMetaReal(id)
|
||||
if (!info) throw new Error('No track with this ID in external list.')
|
||||
let file = await dl.fetchVideo(info)
|
||||
let filename = info.artist + ' - ' + info.title + '.mp3'
|
||||
info.file = path.join(values.directory, filename)
|
||||
await asn.promiseExec(`ffmpeg -i "${file.source}" -metadata artist="${info.artist}" -metadata title="${info.title}" -codec copy "${info.file}"`)
|
||||
await fs.unlink(file.source)
|
||||
let db = await dbPromise
|
||||
let ins = await asn.insertDB(db, info)
|
||||
if (!ins) {
|
||||
throw new Error('A track of this description already exists in the database.')
|
||||
}
|
||||
}
|
||||
|
||||
async function getTrackMetaReal (id) {
|
||||
if (!id || !externalTracks[id]) return null
|
||||
let trdata = externalTracks[id]
|
||||
|
||||
// Check for expiry
|
||||
if (trdata.file && trdata.expires > Date.now()) {
|
||||
return Object.assign({}, trdata)
|
||||
}
|
||||
|
||||
let trsrch = 'ytsearch3:' + trdata.artist + ' - ' + trdata.title
|
||||
let dldata = await dl.getVideoInfo(trsrch)
|
||||
|
||||
let bestMatch
|
||||
if (dldata.length === 1) bestMatch = dldata[0]
|
||||
|
||||
let candidates = []
|
||||
for (let i in dldata) {
|
||||
let obj = dldata[i]
|
||||
let title = obj.title.toLowerCase()
|
||||
|
||||
// Skip any video with 'video' in it, but keep lyric videos
|
||||
if (title.indexOf('video') !== -1 && title.indexOf('lyric') === -1) continue
|
||||
|
||||
// If the title has 'audio' in it, it might be the best match
|
||||
if (title.indexOf('audio') !== -1) {
|
||||
bestMatch = obj
|
||||
break
|
||||
}
|
||||
|
||||
candidates.push(obj)
|
||||
}
|
||||
|
||||
if (candidates.length && !bestMatch) {
|
||||
// Sort candidates by view count
|
||||
candidates = candidates.sort(function (a, b) {
|
||||
return b.view_count - a.view_count
|
||||
})
|
||||
|
||||
// Select the one with the most views
|
||||
bestMatch = candidates[0]
|
||||
}
|
||||
|
||||
// If there were no suitable candidates, just take the first response
|
||||
if (!candidates.length && !bestMatch) bestMatch = dldata[0]
|
||||
|
||||
externalTracks[id] = {
|
||||
id: trdata.id,
|
||||
title: trdata.title,
|
||||
artist: trdata.artist,
|
||||
file: bestMatch.url,
|
||||
duration: bestMatch.duration,
|
||||
expires: Date.now() + memexpire * 1000,
|
||||
external: true
|
||||
}
|
||||
|
||||
return Object.assign({}, externalTracks[id])
|
||||
}
|
||||
|
||||
async function search (track, limit = 30) {
|
||||
if (!values.lastfm) return []
|
||||
|
||||
@ -110,9 +30,9 @@ async function search (track, limit = 30) {
|
||||
return []
|
||||
}
|
||||
|
||||
let final = []
|
||||
for (let i in data.results.trackmatches.track) {
|
||||
let res = data.results.trackmatches.track[i]
|
||||
const final = []
|
||||
for (const i in data.results.trackmatches.track) {
|
||||
const res = data.results.trackmatches.track[i]
|
||||
let clean = {
|
||||
id: createHash(res),
|
||||
artist: res.artist,
|
||||
@ -121,13 +41,7 @@ async function search (track, limit = 30) {
|
||||
mbid: res.mbid
|
||||
}
|
||||
|
||||
if (externalTracks[clean.id]) {
|
||||
// Copy object
|
||||
clean = Object.assign({}, externalTracks[clean.id])
|
||||
} else {
|
||||
// Save in cache
|
||||
externalTracks[clean.id] = clean
|
||||
}
|
||||
clean = addListExternal(clean.id, clean)
|
||||
|
||||
final.push(clean)
|
||||
}
|
||||
@ -135,42 +49,21 @@ async function search (track, limit = 30) {
|
||||
return final
|
||||
}
|
||||
|
||||
// Download thread
|
||||
let dltd = null
|
||||
function invokeDownload (add) {
|
||||
if (add) downloadQueue.push(add)
|
||||
if (dltd) return
|
||||
dltd = setTimeout(function (argument) {
|
||||
dltd = null
|
||||
if (downloading) return invokeDownload()
|
||||
if (!downloadQueue.length) return
|
||||
downloading = true
|
||||
downloadLocally(downloadQueue.shift()).then(() => {
|
||||
downloading = false
|
||||
if (downloadQueue.length) invokeDownload()
|
||||
}).catch((e) => {
|
||||
console.error(e)
|
||||
downloading = false
|
||||
if (downloadQueue.length) invokeDownload()
|
||||
})
|
||||
}, 2 * 1000)
|
||||
}
|
||||
|
||||
// Authentication
|
||||
|
||||
function getAPISig (params) {
|
||||
let allStrings = []
|
||||
let qs = {}
|
||||
params['api_key'] = values.lastfm.key
|
||||
for (let key in params) {
|
||||
let val = params[key]
|
||||
const qs = {}
|
||||
params.api_key = values.lastfm.key
|
||||
for (const key in params) {
|
||||
const val = params[key]
|
||||
if (val == null || val === '') continue
|
||||
allStrings.push(key + val)
|
||||
qs[key] = val
|
||||
}
|
||||
allStrings = allStrings.sort()
|
||||
allStrings.push(values.lastfm.secret)
|
||||
qs['api_sig'] = crypto.createHash('md5').update(allStrings.join('')).digest('hex')
|
||||
qs.api_sig = crypto.createHash('md5').update(allStrings.join('')).digest('hex')
|
||||
return qs
|
||||
}
|
||||
|
||||
@ -179,18 +72,18 @@ function getAuthURL () {
|
||||
}
|
||||
|
||||
async function getSession (token) {
|
||||
let sessSig = getAPISig({ token, method: 'auth.getSession' })
|
||||
let res = await rp('http://ws.audioscrobbler.com/2.0/', { qs: sessSig })
|
||||
let rep = await parseStringPromise(res)
|
||||
let name = rep.lfm.session[0].name[0]
|
||||
let key = rep.lfm.session[0].key[0]
|
||||
const sessSig = getAPISig({ token, method: 'auth.getSession' })
|
||||
const res = await rp('http://ws.audioscrobbler.com/2.0/', { qs: sessSig })
|
||||
const rep = await parseStringPromise(res)
|
||||
const name = rep.lfm.session[0].name[0]
|
||||
const key = rep.lfm.session[0].key[0]
|
||||
return { name, key }
|
||||
}
|
||||
|
||||
async function storeSession (userId, session) {
|
||||
if (!session.name || !session.key) throw new Error('Invalid session parameter.')
|
||||
let db = await dbPromise
|
||||
let existing = await db.get('SELECT * FROM LastFM WHERE userId = ?', userId)
|
||||
const db = await dbPromise
|
||||
const existing = await db.get('SELECT * FROM LastFM WHERE userId = ?', userId)
|
||||
if (existing) {
|
||||
await db.run('UPDATE LastFM SET name = ?, key = ? WHERE userId = ?', session.name, session.key, userId)
|
||||
} else {
|
||||
@ -200,19 +93,19 @@ async function storeSession (userId, session) {
|
||||
}
|
||||
|
||||
async function disregardSession (userId) {
|
||||
let db = await dbPromise
|
||||
const db = await dbPromise
|
||||
return db.run('DELETE FROM LastFM WHERE userId = ?', userId)
|
||||
}
|
||||
|
||||
async function getSessionForUser (userId) {
|
||||
let db = await dbPromise
|
||||
const db = await dbPromise
|
||||
return db.get('SELECT * FROM LastFM WHERE userId = ?', userId)
|
||||
}
|
||||
|
||||
async function scrobbleTrack (userId, trackData) {
|
||||
let sess = await getSessionForUser(userId)
|
||||
const sess = await getSessionForUser(userId)
|
||||
if (!sess) throw new Error('User does not have a LastFM session.')
|
||||
let scrobbleSig = getAPISig({
|
||||
const scrobbleSig = getAPISig({
|
||||
sk: sess.key,
|
||||
method: 'track.scrobble',
|
||||
artist: trackData.artist || 'Unknown',
|
||||
@ -228,5 +121,13 @@ async function scrobbleTrack (userId, trackData) {
|
||||
return true
|
||||
}
|
||||
|
||||
export default { search, getTrackMetaReal, invokeDownload, getAPISig, getAuthURL, getSession,
|
||||
getSessionForUser, storeSession, disregardSession, scrobbleTrack }
|
||||
export default {
|
||||
search,
|
||||
getAPISig,
|
||||
getAuthURL,
|
||||
getSession,
|
||||
getSessionForUser,
|
||||
storeSession,
|
||||
disregardSession,
|
||||
scrobbleTrack
|
||||
}
|
||||
|
@ -1,34 +1,33 @@
|
||||
import path from 'path'
|
||||
import { dbPromise } from './database'
|
||||
|
||||
async function getPlaylist (id, order = 'ntry.indx', direction = 'ASC') {
|
||||
let db = await dbPromise
|
||||
let p = await db.get('SELECT * FROM Playlist WHERE id = ?', id)
|
||||
const db = await dbPromise
|
||||
const p = await db.get('SELECT * FROM Playlist WHERE id = ?', id)
|
||||
if (!p) throw new Error('No such playlist!')
|
||||
let q = await db.all('SELECT ntry.id,ntry.trackId,ntry.playlistId,ntry.indx,ntry.userId, \
|
||||
trck.title,trck.artist,trck.genre,trck.year,trck.duration,trck.track,trck.album \
|
||||
FROM PlaylistEntry ntry LEFT JOIN Track \
|
||||
trck ON ntry.trackId = trck.id WHERE ntry.playlistId = ? ORDER BY ' + order + ' ' + direction.toUpperCase(), id)
|
||||
const q = await db.all(`SELECT ntry.id,ntry.trackId,ntry.playlistId,ntry.indx,ntry.userId,
|
||||
trck.title,trck.artist,trck.genre,trck.year,trck.duration,trck.track,trck.album
|
||||
FROM PlaylistEntry ntry INNER JOIN Track
|
||||
trck ON ntry.trackId = trck.id WHERE ntry.playlistId = ? ORDER BY ${order} ${direction}`, id)
|
||||
p.tracks = q || []
|
||||
return p
|
||||
}
|
||||
|
||||
async function getPlaylists (userId) {
|
||||
let db = await dbPromise
|
||||
const db = await dbPromise
|
||||
return db.all('SELECT * FROM Playlist WHERE userId = ? OR userId = NULL', userId)
|
||||
}
|
||||
|
||||
async function createPlaylist (userId, title) {
|
||||
let db = await dbPromise
|
||||
let alreadyExists = await db.get('SELECT * FROM Playlist WHERE userId = ? AND title = ?', userId, title)
|
||||
const db = await dbPromise
|
||||
const alreadyExists = await db.get('SELECT * FROM Playlist WHERE userId = ? AND title = ?', userId, title)
|
||||
if (alreadyExists) throw new Error('Playlist with the same name already exists!')
|
||||
await db.run('INSERT INTO Playlist (title,userId) VALUES (?,?)', title, userId)
|
||||
return db.get('SELECT id FROM Playlist WHERE title = ? AND userId = ?', title, userId)
|
||||
}
|
||||
|
||||
async function deletePlaylist (userId, playlistId) {
|
||||
let db = await dbPromise
|
||||
let ply = await db.get('SELECT * FROM Playlist WHERE id = ?', playlistId)
|
||||
const db = await dbPromise
|
||||
const ply = await db.get('SELECT * FROM Playlist WHERE id = ?', playlistId)
|
||||
if (!ply) throw new Error('Could not find playlist specified.')
|
||||
if (ply.userId !== userId) throw new Error(ply.userId == null ? 'This public playlist cannot be deleted through the interface.' : 'Permission denied.')
|
||||
db.run('DELETE Playlist WHERE id = ?', playlistId)
|
||||
@ -37,14 +36,14 @@ async function deletePlaylist (userId, playlistId) {
|
||||
}
|
||||
|
||||
async function addTrack (userId, playlistId, trackId) {
|
||||
let db = await dbPromise
|
||||
let p = await getPlaylist(playlistId)
|
||||
const db = await dbPromise
|
||||
const p = await getPlaylist(playlistId)
|
||||
if (!p) throw new Error('Invalid playlist.')
|
||||
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.')
|
||||
|
||||
let alreadyExists = false
|
||||
for (let i in p.tracks) {
|
||||
let tr = p.tracks[i]
|
||||
for (const i in p.tracks) {
|
||||
const tr = p.tracks[i]
|
||||
if (tr.trackId === parseInt(trackId)) {
|
||||
alreadyExists = true
|
||||
break
|
||||
@ -57,29 +56,29 @@ async function addTrack (userId, playlistId, trackId) {
|
||||
}
|
||||
|
||||
async function removeTrack (userId, playlistId, trackId) {
|
||||
let db = await dbPromise
|
||||
let p = await getPlaylist(playlistId)
|
||||
const db = await dbPromise
|
||||
const p = await getPlaylist(playlistId)
|
||||
if (!p) throw new Error('Invalid playlist.')
|
||||
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.')
|
||||
|
||||
let trackEntry = await db.get('SELECT * FROM PlaylistEntry WHERE playlistId = ? AND trackId = ?', playlistId, trackId)
|
||||
const trackEntry = await db.get('SELECT * FROM PlaylistEntry WHERE playlistId = ? AND trackId = ?', playlistId, trackId)
|
||||
if (!trackEntry) throw new Error('This track is not in the playlist.')
|
||||
|
||||
return db.run('DELETE FROM PlaylistEntry WHERE id = ?', trackEntry.id)
|
||||
}
|
||||
|
||||
async function moveTrack (userId, playlistId, trackId, position) {
|
||||
let db = await dbPromise
|
||||
let p = await getPlaylist(playlistId)
|
||||
const db = await dbPromise
|
||||
const p = await getPlaylist(playlistId)
|
||||
if (!p) throw new Error('Invalid playlist.')
|
||||
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.')
|
||||
|
||||
let trackEntry = await db.get('SELECT * FROM PlaylistEntry WHERE playlistId = ? AND trackId = ?', playlistId, trackId)
|
||||
const trackEntry = await db.get('SELECT * FROM PlaylistEntry WHERE playlistId = ? AND trackId = ?', playlistId, trackId)
|
||||
if (!trackEntry) throw new Error('This track is not in the playlist.')
|
||||
|
||||
let trcksNew = []
|
||||
for (let i in p.tracks) {
|
||||
let trck = p.tracks[i]
|
||||
const trcksNew = []
|
||||
for (const i in p.tracks) {
|
||||
const trck = p.tracks[i]
|
||||
if (trck.trackId === trackId) {
|
||||
trck.indx = position
|
||||
continue
|
||||
@ -91,8 +90,8 @@ async function moveTrack (userId, playlistId, trackId, position) {
|
||||
}
|
||||
|
||||
// Update indexes
|
||||
for (let i in trcksNew) {
|
||||
let trck = trcksNew[i]
|
||||
for (const i in trcksNew) {
|
||||
const trck = trcksNew[i]
|
||||
await db.run('UPDATE PlaylistEntry SET indx = ? WHERE trackId = ? AND playlistId = ?', trck.indx, trck.trackId, playlistId)
|
||||
}
|
||||
|
||||
|
126
src/server.js
126
src/server.js
@ -5,7 +5,6 @@ import bodyParser from 'body-parser'
|
||||
import connectSession from 'connect-redis'
|
||||
import redis from 'redis'
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
import ffmpeg from 'fluent-ffmpeg'
|
||||
import { user, userMiddleware } from './user'
|
||||
import { dbPromise } from './database'
|
||||
@ -14,6 +13,7 @@ import asn from './common/async'
|
||||
|
||||
import playlist from './playlist'
|
||||
import lastfm from './lastfm'
|
||||
import external from './external.js'
|
||||
|
||||
require('express-async-errors')
|
||||
|
||||
@ -40,7 +40,7 @@ const router = express.Router()
|
||||
const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year']
|
||||
const srchcategories = ['title', 'artist', 'album']
|
||||
|
||||
let SessionStore = connectSession(session)
|
||||
const SessionStore = connectSession(session)
|
||||
app.use(session({
|
||||
key: values.session_key || 'Session',
|
||||
secret: values.session_secret || 'ch4ng3 m3!',
|
||||
@ -73,17 +73,17 @@ router.get('/tracks', userMiddleware, async (req, res) => {
|
||||
sortdir = 'asc'
|
||||
}
|
||||
|
||||
let db = await dbPromise
|
||||
let count = (await db.get('SELECT COUNT(*) FROM Track'))['COUNT(*)']
|
||||
const db = await dbPromise
|
||||
const count = (await db.get('SELECT COUNT(*) as \'count\' FROM Track')).count
|
||||
|
||||
let pageCount = Math.ceil(count / tracksPerPage)
|
||||
const 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)
|
||||
const offset = (page - 1) * tracksPerPage
|
||||
const tracks = await db.all(`SELECT * FROM Track ORDER BY ${sort} ${sortdir} LIMIT ? OFFSET ?`, tracksPerPage, offset)
|
||||
|
||||
for (let i in tracks) {
|
||||
for (const i in tracks) {
|
||||
delete tracks[i].file
|
||||
}
|
||||
|
||||
@ -93,13 +93,21 @@ router.get('/tracks', userMiddleware, async (req, res) => {
|
||||
})
|
||||
|
||||
router.get('/tracks/search', userMiddleware, async (req, res) => {
|
||||
const streamable = (req.query.streamable === '1')
|
||||
let query = req.query.q
|
||||
let streamable = (req.query.streamable === '1')
|
||||
let qr = ''
|
||||
let exact = false
|
||||
|
||||
if (query.indexOf('http') !== -1) {
|
||||
const result = await external.searchURL(query)
|
||||
res.jsonp({
|
||||
page: 1, count: 1, pageCount: 1, tracks: result
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (query.indexOf(':') !== -1) {
|
||||
let ctr = query.split(':')
|
||||
const ctr = query.split(':')
|
||||
|
||||
if (srchcategories.indexOf(ctr[0]) !== -1) {
|
||||
qr = `ifnull(${ctr[0]}, '')`
|
||||
@ -108,8 +116,8 @@ router.get('/tracks/search', userMiddleware, async (req, res) => {
|
||||
}
|
||||
|
||||
if (qr === '') {
|
||||
for (let c in srchcategories) {
|
||||
let cat = srchcategories[c]
|
||||
for (const c in srchcategories) {
|
||||
const cat = srchcategories[c]
|
||||
if (parseInt(c) !== 0) qr += ' || '
|
||||
qr += `ifnull(${cat}, '')`
|
||||
}
|
||||
@ -120,7 +128,7 @@ router.get('/tracks/search', userMiddleware, async (req, res) => {
|
||||
exact = true
|
||||
}
|
||||
|
||||
let original = String(query)
|
||||
const original = String(query)
|
||||
if (!exact) query = `%${query}%`
|
||||
|
||||
let sort = req.query.sort
|
||||
@ -139,31 +147,31 @@ router.get('/tracks/search', userMiddleware, async (req, res) => {
|
||||
page = 1
|
||||
}
|
||||
|
||||
let db = await dbPromise
|
||||
let count = (await db.get(`SELECT COUNT(*) FROM Track WHERE ${qr} LIKE ?`, query))['COUNT(*)']
|
||||
const db = await dbPromise
|
||||
let count = (await db.get(`SELECT COUNT(*) as '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 ?`,
|
||||
const offset = (page - 1) * tracksPerPage
|
||||
let tracks = await db.all(`SELECT * FROM Track WHERE ${qr} LIKE ? ORDER BY ${sort} ${sortdir} 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)
|
||||
const 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
|
||||
if (page === 0) page = 1
|
||||
if (pageCount === 0) pageCount = 1
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
for (let i in tracks) {
|
||||
for (const i in tracks) {
|
||||
delete tracks[i].file
|
||||
}
|
||||
|
||||
@ -173,12 +181,12 @@ router.get('/tracks/search', userMiddleware, async (req, res) => {
|
||||
})
|
||||
|
||||
router.get('/track/:id', userMiddleware, async (req, res, next) => {
|
||||
let id = req.params.id
|
||||
const id = req.params.id
|
||||
|
||||
let db = await dbPromise
|
||||
const db = await dbPromise
|
||||
let track = await db.get('SELECT * FROM Track WHERE id = ?', id)
|
||||
if (!track) {
|
||||
track = await lastfm.getTrackMetaReal(id)
|
||||
track = await external.getTrackMetaReal(id)
|
||||
if (!track) throw new Error('404 track not found')
|
||||
}
|
||||
|
||||
@ -188,14 +196,14 @@ router.get('/track/:id', userMiddleware, async (req, res, next) => {
|
||||
})
|
||||
|
||||
router.post('/track/:id', userMiddleware, async (req, res, next) => {
|
||||
let id = req.params.id
|
||||
let meta = req.body
|
||||
const id = req.params.id
|
||||
const meta = req.body
|
||||
|
||||
let db = await dbPromise
|
||||
let track = await db.get('SELECT file FROM Track WHERE id = ?', id)
|
||||
const db = await dbPromise
|
||||
const track = await db.get('SELECT file FROM Track WHERE id = ?', id)
|
||||
if (!track) throw new Error('404 track not found')
|
||||
|
||||
let m = await asn.setMetadata(track.file, meta)
|
||||
const m = await asn.setMetadata(track.file, meta)
|
||||
await asn.updateDB(db, id, m.dbq)
|
||||
|
||||
res.jsonp(m)
|
||||
@ -209,39 +217,39 @@ router.post('/track/:id', userMiddleware, async (req, res, next) => {
|
||||
|
||||
router.post('/playlist/new', userMiddleware, async (req, res, next) => {
|
||||
if (!req.body.title) throw new Error('Title missing from body.')
|
||||
let id = await playlist.createPlaylist(req.session.user, req.body.title)
|
||||
res.jsonp({success: true, playlist: id.id})
|
||||
const id = await playlist.createPlaylist(req.session.user, req.body.title)
|
||||
res.jsonp({ success: true, playlist: id.id })
|
||||
})
|
||||
|
||||
router.post('/playlist/delete/:playlistId', userMiddleware, async (req, res, next) => {
|
||||
let pId = req.params.playlistId
|
||||
const pId = req.params.playlistId
|
||||
await playlist.deletePlaylist(req.session.user, pId)
|
||||
res.jsonp({success: true})
|
||||
res.jsonp({ success: true })
|
||||
})
|
||||
|
||||
// Playlist track endpoints
|
||||
|
||||
router.post('/playlist/track/put/:playlistId/:trackId', userMiddleware, async (req, res, next) => {
|
||||
let pId = req.params.playlistId
|
||||
let tId = req.params.trackId
|
||||
const pId = req.params.playlistId
|
||||
const tId = req.params.trackId
|
||||
await playlist.addTrack(req.session.user, pId, tId)
|
||||
res.jsonp({success: true})
|
||||
res.jsonp({ success: true })
|
||||
})
|
||||
|
||||
router.post('/playlist/track/remove/:playlistId/:trackId', userMiddleware, async (req, res, next) => {
|
||||
let pId = req.params.playlistId
|
||||
let tId = req.params.trackId
|
||||
const pId = req.params.playlistId
|
||||
const tId = req.params.trackId
|
||||
await playlist.removeTrack(req.session.user, pId, tId)
|
||||
res.jsonp({success: true})
|
||||
res.jsonp({ success: true })
|
||||
})
|
||||
|
||||
router.post('/playlist/track/move/:playlistId/:trackId', userMiddleware, async (req, res, next) => {
|
||||
let pId = req.params.playlistId
|
||||
let tId = req.params.trackId
|
||||
let pos = parseInt(req.body.position)
|
||||
const pId = req.params.playlistId
|
||||
const tId = req.params.trackId
|
||||
const pos = parseInt(req.body.position)
|
||||
if (!pos || isNaN(pos)) throw new Error('Invalid position.')
|
||||
await playlist.moveTrack(req.session.user, pId, tId, pos)
|
||||
res.jsonp({success: true})
|
||||
res.jsonp({ success: true })
|
||||
})
|
||||
|
||||
router.get('/playlist', userMiddleware, async (req, res, next) => {
|
||||
@ -284,7 +292,7 @@ async function scrobble (user, track) {
|
||||
}
|
||||
|
||||
router.get('/lastfm', userMiddleware, async (req, res, next) => {
|
||||
let sess = await lastfm.getSessionForUser(req.session.user)
|
||||
const sess = await lastfm.getSessionForUser(req.session.user)
|
||||
if (sess) return res.jsonp({ connected: true, name: sess.name })
|
||||
res.jsonp({ connected: false })
|
||||
})
|
||||
@ -300,20 +308,20 @@ router.get('/lastfm/disconnect', userMiddleware, async (req, res, next) => {
|
||||
})
|
||||
|
||||
router.get('/lastfm/_redirect', userMiddleware, async (req, res, next) => {
|
||||
let token = req.query.token
|
||||
const token = req.query.token
|
||||
if (!token) throw new Error('Failed to get token from LastFM!')
|
||||
let session = await lastfm.getSession(token)
|
||||
const session = await lastfm.getSession(token)
|
||||
await lastfm.storeSession(req.session.user, session)
|
||||
res.redirect('/?success=lastfm')
|
||||
})
|
||||
|
||||
router.post('/lastfm/scrobble/:track', userMiddleware, async (req, res, next) => {
|
||||
let id = req.params.track
|
||||
let user = req.session.user
|
||||
let db = await dbPromise
|
||||
const id = req.params.track
|
||||
const user = req.session.user
|
||||
const db = await dbPromise
|
||||
let track = await db.get('SELECT title,artist,album,duration FROM Track WHERE id = ?', id)
|
||||
if (!track) {
|
||||
track = await lastfm.getTrackMetaReal(id)
|
||||
track = await external.getTrackMetaReal(id)
|
||||
if (!track) throw new Error('404 file not found')
|
||||
}
|
||||
await scrobble(user, track)
|
||||
@ -325,26 +333,26 @@ router.post('/lastfm/scrobble/:track', userMiddleware, async (req, res, next) =>
|
||||
// ------------ //
|
||||
|
||||
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
|
||||
const id = req.params.id
|
||||
const dl = (req.query.dl === '1')
|
||||
const db = await dbPromise
|
||||
let track = await db.get('SELECT file FROM Track WHERE id = ?', id)
|
||||
if (!track) {
|
||||
track = await lastfm.getTrackMetaReal(id)
|
||||
track = await external.getTrackMetaReal(id)
|
||||
if (!track) throw new Error('404 file not found')
|
||||
if (dl) {
|
||||
lastfm.invokeDownload(id)
|
||||
external.invokeDownload(id)
|
||||
return res.end('<p>OK</p><script>window.close();</script>')
|
||||
}
|
||||
dev && console.log("Remote", track.file)
|
||||
dev && console.log('Remote', track.file)
|
||||
return ffmpeg(track.file)
|
||||
.audioCodec('libmp3lame')
|
||||
.format('mp3')
|
||||
.on('error', (e) => console.error(e))
|
||||
.pipe(res, {end: true})
|
||||
.pipe(res, { end: true })
|
||||
}
|
||||
|
||||
let fpath = path.resolve(track.file)
|
||||
const fpath = path.resolve(track.file)
|
||||
|
||||
res.set('Cache-Control', 'public, max-age=31557600')
|
||||
if (dl) return res.download(fpath)
|
||||
@ -357,7 +365,7 @@ router.get('/serve/by-id/:id', userMiddleware, async (req, res, next) => {
|
||||
// ---------- //
|
||||
|
||||
router.use((err, req, res, next) => {
|
||||
let msg = err.message
|
||||
const msg = err.message
|
||||
dev && console.error(err.stack)
|
||||
res.status(msg.indexOf('404') !== -1 ? 404 : 400).jsonp({ error: err.message, stack: dev ? err.stack.toString() : undefined })
|
||||
})
|
||||
|
50
src/user.js
50
src/user.js
@ -7,8 +7,8 @@ import { dbPromise } from './database'
|
||||
const router = express.Router()
|
||||
|
||||
async function userInfoPublic (id) {
|
||||
let db = await dbPromise
|
||||
let u = await db.get('SELECT id, username, image FROM User WHERE id = ?', id)
|
||||
const db = await dbPromise
|
||||
const u = await db.get('SELECT id, username, image FROM User WHERE id = ?', id)
|
||||
if (!u) return {}
|
||||
return {
|
||||
id: u.id,
|
||||
@ -18,7 +18,7 @@ async function userInfoPublic (id) {
|
||||
}
|
||||
|
||||
export async function userInfo (id) {
|
||||
let db = await dbPromise
|
||||
const db = await dbPromise
|
||||
return db.get('SELECT * FROM User WHERE id = ?', id)
|
||||
}
|
||||
|
||||
@ -39,11 +39,11 @@ export function user (oauth, registrations) {
|
||||
|
||||
if (!oauth) return router
|
||||
|
||||
let oauth2 = new PromiseOAuth2(oauth.clientId, oauth.clientSecret, oauth.baseUrl, oauth.authorizePath, oauth.tokenPath)
|
||||
const oauth2 = new PromiseOAuth2(oauth.clientId, oauth.clientSecret, oauth.baseUrl, oauth.authorizePath, oauth.tokenPath)
|
||||
|
||||
router.get('/login/oauth/_redirect', async (req, res) => {
|
||||
let code = req.query.code
|
||||
let state = req.query.state
|
||||
const code = req.query.code
|
||||
const state = req.query.state
|
||||
if (!code || !state) throw new Error('Something went wrong!')
|
||||
if (!req.session || !req.session.oauthState || req.session.oauthState !== state) throw new Error('Possible request forgery detected! Try again.')
|
||||
|
||||
@ -56,7 +56,7 @@ export function user (oauth, registrations) {
|
||||
throw new Error('No authorization!')
|
||||
}
|
||||
|
||||
let accessToken = tokens[2].access_token
|
||||
const accessToken = tokens[2].access_token
|
||||
|
||||
// Get user information on remote
|
||||
let userInfo
|
||||
@ -70,8 +70,8 @@ export function user (oauth, registrations) {
|
||||
if (!userInfo) throw new Error('Couldn\'t get user information!')
|
||||
|
||||
// Let's see if there's a link for this user already..
|
||||
let db = await dbPromise
|
||||
let userLocal = await db.get('SELECT * FROM OAuth WHERE remoteId = ?', userInfo.id)
|
||||
const db = await dbPromise
|
||||
const userLocal = await db.get('SELECT * FROM OAuth WHERE remoteId = ?', userInfo.id)
|
||||
|
||||
// User and link both exist
|
||||
if (userLocal) {
|
||||
@ -92,7 +92,7 @@ export function user (oauth, registrations) {
|
||||
// Create a new user and log in
|
||||
await db.run('INSERT INTO User (username,email,image,created) VALUES (?,?,?,?)', userInfo.username, userInfo.email, userInfo.image, new Date())
|
||||
|
||||
let newU = await db.get('SELECT * FROM User WHERE username = ?', userInfo.username)
|
||||
const newU = await db.get('SELECT * FROM User WHERE username = ?', userInfo.username)
|
||||
|
||||
if (!newU) throw new Error('Something went wrong!')
|
||||
|
||||
@ -103,27 +103,27 @@ export function user (oauth, registrations) {
|
||||
})
|
||||
|
||||
router.get('/login/oauth', async (req, res) => {
|
||||
let state = crypto.randomBytes(16).toString('hex')
|
||||
const state = crypto.randomBytes(16).toString('hex')
|
||||
req.session.oauthState = state
|
||||
|
||||
return res.redirect(oauth2.getAuthorizeUrl({
|
||||
'redirect_uri': oauth.redirectUri,
|
||||
'scope': oauth.scope,
|
||||
'response_type': 'code',
|
||||
'state': state
|
||||
redirect_uri: oauth.redirectUri,
|
||||
scope: oauth.scope,
|
||||
response_type: 'code',
|
||||
state: state
|
||||
}))
|
||||
})
|
||||
|
||||
router.use('/login', async (req, res, next) => {
|
||||
if (req.session && req.session.user) return res.redirect('/')
|
||||
let header = req.get('authorization') || ''
|
||||
let token = header.split(/\s+/).pop() || ''
|
||||
let auth = Buffer.from(token, 'base64').toString()
|
||||
let parts = auth.split(/:/)
|
||||
let username = parts[0]
|
||||
let password = parts[1]
|
||||
const header = req.get('authorization') || ''
|
||||
const token = header.split(/\s+/).pop() || ''
|
||||
const auth = Buffer.from(token, 'base64').toString()
|
||||
const parts = auth.split(/:/)
|
||||
const username = parts[0]
|
||||
const password = parts[1]
|
||||
|
||||
let message = oauth != null ? 'Enter \'oauth\' to log in remotely.' : 'Log in'
|
||||
const message = oauth != null ? 'Enter \'oauth\' to log in remotely.' : 'Log in'
|
||||
req.message = message
|
||||
|
||||
if ((!username || !password) && (username !== 'oauth' && oauth)) {
|
||||
@ -134,8 +134,8 @@ export function user (oauth, registrations) {
|
||||
return res.redirect('/user/login/oauth')
|
||||
}
|
||||
|
||||
let db = await dbPromise
|
||||
let user = await db.get('SELECT * FROM User WHERE username = ?', username)
|
||||
const db = await dbPromise
|
||||
const user = await db.get('SELECT * FROM User WHERE username = ?', username)
|
||||
if (!user) return next()
|
||||
|
||||
if (!user.password && oauth) {
|
||||
@ -143,7 +143,7 @@ export function user (oauth, registrations) {
|
||||
}
|
||||
|
||||
// Compare passwords
|
||||
let ures = await bcrypt.compare(password, user.password)
|
||||
const ures = await bcrypt.compare(password, user.password)
|
||||
if (!ures) return next()
|
||||
|
||||
// Set login success
|
||||
|
Loading…
Reference in New Issue
Block a user