listen by youtube url, fix context menu positioning

This commit is contained in:
Evert Prants 2020-09-26 09:06:53 +03:00
parent 7a3ad95d60
commit 710f1a5e48
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
9 changed files with 353 additions and 304 deletions

View File

@ -170,11 +170,11 @@
return false return false
} }
function getPosition(e) { function getPosition (e) {
var posx = 0 var posx = 0
var posy = 0 var posy = 0
if (!e) var e = window.event if (!e) e = window.event
if (e.pageX || e.pageY) { if (e.pageX || e.pageY) {
posx = e.pageX posx = e.pageX
@ -192,25 +192,25 @@
} }
} }
function positionMenu(e) { function positionMenu (e) {
clickCoords = getPosition(e) const clickCoords = getPosition(e)
clickCoordsX = clickCoords.x const clickCoordsX = clickCoords.x
clickCoordsY = clickCoords.y const clickCoordsY = clickCoords.y
menuWidth = menu.offsetWidth + 4 const menuWidth = menu.offsetWidth + 4
menuHeight = menu.offsetHeight + 4 const menuHeight = menu.offsetHeight + 4
windowWidth = window.innerWidth const windowWidth = window.innerWidth
windowHeight = window.innerHeight const windowHeight = window.innerHeight - 80
if ( (windowWidth - clickCoordsX) < menuWidth ) { if ((windowWidth - clickCoordsX) < menuWidth) {
menu.style.left = windowWidth - menuWidth + 'px' menu.style.left = (windowWidth - menuWidth) + 'px'
} else { } else {
menu.style.left = clickCoordsX + 'px' menu.style.left = clickCoordsX + 'px'
} }
if ( (windowHeight - clickCoordsY) < menuHeight ) { if ((windowHeight - clickCoordsY) < menuHeight) {
menu.style.top = windowHeight - menuHeight + 'px' menu.style.top = (windowHeight - menuHeight) + 'px'
} else { } else {
menu.style.top = clickCoordsY + 'px' menu.style.top = clickCoordsY + 'px'
} }
@ -330,7 +330,6 @@
} }
function ctxTrack (xid, ev, qe) { function ctxTrack (xid, ev, qe) {
positionMenu(ev)
menu.style.display = 'block' menu.style.display = 'block'
let qbtn = menu.querySelector('.ctx-item[data-action="queue"]') let qbtn = menu.querySelector('.ctx-item[data-action="queue"]')
@ -345,6 +344,8 @@
let plDel = menu.querySelector('.ctx-item[data-action="playlist-remove"]') let plDel = menu.querySelector('.ctx-item[data-action="playlist-remove"]')
plDel.style.display = (playlist != null && playlist >= 0) ? 'block' : 'none' plDel.style.display = (playlist != null && playlist >= 0) ? 'block' : 'none'
positionMenu(ev)
ctxState = xid ctxState = xid
} }
@ -825,7 +826,7 @@
ctxHide() ctxHide()
}, false) }, false)
document.addEventListener('click', function(e) { document.addEventListener('click', function (e) {
var clickeElIsLink = clickInsideElement(e, 'ctx-item') var clickeElIsLink = clickInsideElement(e, 'ctx-item')
if (clickeElIsLink) { if (clickeElIsLink) {
@ -833,7 +834,7 @@
ctxHandle(clickeElIsLink) ctxHandle(clickeElIsLink)
} else { } else {
var button = e.which || e.button var button = e.which || e.button
if ( button === 1 ) { if (button === 1) {
ctxHide() ctxHide()
} }
} }

View File

@ -1,8 +1,7 @@
import {spawn} from 'child_process' import { spawn } from 'child_process'
import ffmpeg from 'fluent-ffmpeg' import ffmpeg from 'fluent-ffmpeg'
import path from 'path' import path from 'path'
import asn from './async'
function parseTitle (data) { function parseTitle (data) {
let tt = data.title let tt = data.title
@ -11,16 +10,16 @@ function parseTitle (data) {
tt = tt.replace(/^\[\w+\]\s?/i, '') tt = tt.replace(/^\[\w+\]\s?/i, '')
// Remove "Official Video/Audio" tag // Remove "Official Video/Audio" tag
tt = tt.replace(/\s?(?:[\(\)\[\]])?Official\s?[\w]+(?:[\(\)\[\]])?/i, '') tt = tt.replace(/\s?(?:[()[]])?Official\s?[\w]+(?:[()[]])?/i, '')
// Remove "Audio" tag // Remove "Audio" tag
tt = tt.replace(/\s?(?:[\(\)\[\]])Audio?(?:[\(\)\[\]])/i, '') tt = tt.replace(/\s?(?:[()[]])Audio?(?:[()[]])/i, '')
// Remove "lyrics" tag // Remove "lyrics" tag
tt = tt.replace(/\s?(?:[\(\)\[\]])?lyrics?\s?(?:[\w]+)?(?:[\(\)\[\]])?\s?/i, '') tt = tt.replace(/\s?(?:[()[]])?lyrics?\s?(?:[\w]+)?(?:[()[]])?\s?/i, '')
// Artist / Title split // Artist / Title split
let at = tt.split(' - ', 2) const at = tt.split(' - ', 2)
let artist let artist
let title let title
@ -39,7 +38,7 @@ function parseTitle (data) {
} }
function getVideoInfo (arg) { 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 = '' let output = ''
yt.stdout.on('data', function (chunk) { yt.stdout.on('data', function (chunk) {
@ -48,10 +47,10 @@ function getVideoInfo (arg) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
yt.on('close', function () { yt.on('close', function () {
let ftdata = output.trim().split('\n') const ftdata = output.trim().split('\n')
if (ftdata.length > 1) { if (ftdata.length > 1) {
let composite = [] const composite = []
for (let i in ftdata) { for (const i in ftdata) {
let dat let dat
try { try {
dat = JSON.parse(ftdata[i]) dat = JSON.parse(ftdata[i])
@ -65,7 +64,7 @@ function getVideoInfo (arg) {
return resolve(composite) return resolve(composite)
} }
let data = JSON.parse(output) const data = JSON.parse(output)
delete data.formats delete data.formats
resolve(data) resolve(data)
}) })
@ -74,7 +73,7 @@ function getVideoInfo (arg) {
function fetchVideo (data) { function fetchVideo (data) {
return new Promise((resolve, reject) => { 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) ffmpeg(data.url || data.file)
.audioCodec('libmp3lame') .audioCodec('libmp3lame')
.format('mp3') .format('mp3')

View File

@ -1,4 +1,3 @@
import fs from 'fs-extra'
import path from 'path' import path from 'path'
import readline from 'readline' 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" // ffprobe -i <file> -show_entries format=duration -v quiet -of csv="p=0"
async function interactive (fpath) { async function interactive (fpath) {
let rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout
}) })
console.log('=> No metadata found for specified file! Interactive mode enabled.\n') console.log('=> No metadata found for specified file! Interactive mode enabled.\n')
let pt = path.parse(fpath) const pt = path.parse(fpath)
let track = { const track = {
file: fpath, file: fpath,
title: pt.name title: pt.name
} }
let clean = dl.parseTitle(track) const clean = dl.parseTitle(track)
console.log('== Determined Title: ' + clean.title) console.log('== Determined Title: ' + clean.title)
console.log('== Determined Artist: ' + clean.artist) console.log('== Determined Artist: ' + clean.artist)
let newTitle = await asn.askAsync(rl, `Title [${clean.title}] ? `) const newTitle = await asn.askAsync(rl, `Title [${clean.title}] ? `)
let newArtist = await asn.askAsync(rl, `Artist [${clean.artist}] ? `) const newArtist = await asn.askAsync(rl, `Artist [${clean.artist}] ? `)
if (newTitle.trim() !== '') if (newTitle.trim() !== '') {
track.title = newTitle track.title = newTitle
}
if (newArtist.trim() !== '') if (newArtist.trim() !== '') {
track.artist = newArtist 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) track.duration = parseFloat(len)
rl.close() rl.close()
@ -47,7 +48,7 @@ async function interactive (fpath) {
} }
async function handlePassed (db, fpath) { async function handlePassed (db, fpath) {
let filePath = path.resolve(fpath) const filePath = path.resolve(fpath)
let trackinf let trackinf
try { try {
@ -60,18 +61,18 @@ async function handlePassed (db, fpath) {
throw new Error('Nothing to do.') throw new Error('Nothing to do.')
} }
let ins = await asn.insertDB(db, trackinf) const ins = await asn.insertDB(db, trackinf)
if (!ins) { if (!ins) {
throw new Error('A track of this description already exists in the database.') throw new Error('A track of this description already exists in the database.')
} }
} }
async function run () { async function run () {
let db = await dbPromise const db = await dbPromise
if (process.argv[2] != null) { if (process.argv[2] != null) {
for (let i = 2; i < process.argv.length; i++) { for (let i = 2; i < process.argv.length; i++) {
let f = process.argv[i] const f = process.argv[i]
console.log('=> Passing', f) console.log('=> Passing', f)
try { try {
await handlePassed(db, f) await handlePassed(db, f)
@ -84,16 +85,16 @@ async function run () {
return return
} }
let files = await asn.getFiles(musicdir) const files = await asn.getFiles(musicdir)
let cleanTrackData = [] const cleanTrackData = []
let skips = 0 let skips = 0
for (let i in files) { for (const i in files) {
let file = files[i] const file = files[i]
// if (cleanTrackData.length > 10) break // debug purposes // if (cleanTrackData.length > 10) break // debug purposes
process.stdout.write(`\rProcessing file ${parseInt(i) + 1} of ${files.length}.. (${skips} skipped)`) process.stdout.write(`\rProcessing file ${parseInt(i) + 1} of ${files.length}.. (${skips} skipped)`)
try { try {
let fd = await asn.getInfos(file) const fd = await asn.getInfos(file)
cleanTrackData.push(fd) cleanTrackData.push(fd)
} catch (e) { } catch (e) {
skips++ skips++
@ -103,11 +104,11 @@ async function run () {
process.stdout.write(`\r${cleanTrackData.length} files indexed, ${skips} files were skipped. \n`) process.stdout.write(`\r${cleanTrackData.length} files indexed, ${skips} files were skipped. \n`)
let entries = 0 let entries = 0
for (let i in cleanTrackData) { for (const i in cleanTrackData) {
let track = cleanTrackData[i] const track = cleanTrackData[i]
process.stdout.write(`\rPopulating database.. (Track ${parseInt(i) + 1} of ${cleanTrackData.length})`) process.stdout.write(`\rPopulating database.. (Track ${parseInt(i) + 1} of ${cleanTrackData.length})`)
try { try {
let ins = await asn.insertDB(db, track) const ins = await asn.insertDB(db, track)
if (!ins) continue if (!ins) continue
entries++ entries++
} catch (e) { } catch (e) {

View File

@ -1,6 +1,5 @@
'use strict' 'use strict'
import fs from 'fs-extra'
import readline from 'readline' import readline from 'readline'
import path from 'path' import path from 'path'
@ -12,19 +11,19 @@ const values = require(path.join(process.cwd(), 'values.json'))
const musicdir = path.resolve(values.directory) const musicdir = path.resolve(values.directory)
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout
}) })
async function download (furl) { async function download (furl) {
console.log('=> Getting information..') console.log('=> Getting information..')
let data = await dl.getVideoInfo(furl) const data = await dl.getVideoInfo(furl)
console.log('=> Downloading file..') console.log('=> Downloading file..')
let file = await dl.fetchVideo(data) const file = await dl.fetchVideo(data)
console.log('=> Cleaning up..') console.log('=> Cleaning up..')
let clean = dl.parseTitle(file) const clean = dl.parseTitle(file)
let filename = clean.artist + ' - ' + clean.title + '.mp3' let filename = clean.artist + ' - ' + clean.title + '.mp3'
console.log('=> Original Title: ' + file.title + '\n') console.log('=> Original Title: ' + file.title + '\n')
@ -32,9 +31,9 @@ async function download (furl) {
console.log('== Determined Artist: ' + clean.artist) console.log('== Determined Artist: ' + clean.artist)
console.log('== Determined File Name: ' + filename) console.log('== Determined File Name: ' + filename)
let titleAnsw = await asn.askAsync(rl, `Title [${clean.title}] ? `) const titleAnsw = await asn.askAsync(rl, `Title [${clean.title}] ? `)
let artistAnsw = await asn.askAsync(rl, `Artist [${clean.artist}] ? `) const artistAnsw = await asn.askAsync(rl, `Artist [${clean.artist}] ? `)
let fileAnsw = await asn.askAsync(rl, `File [${filename}] ? `) const fileAnsw = await asn.askAsync(rl, `File [${filename}] ? `)
if (titleAnsw && titleAnsw.trim() !== '') { if (titleAnsw && titleAnsw.trim() !== '') {
clean.title = titleAnsw clean.title = titleAnsw
@ -48,14 +47,14 @@ async function download (furl) {
filename = fileAnsw filename = fileAnsw
} }
let fn = path.join(musicdir, filename) const fn = path.join(musicdir, filename)
await asn.setMetadata(fn, clean, file.source, true) 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) { if (addAnsw && addAnsw.trim().toLowerCase().indexOf('y') === 0) {
// Add to database // Add to database
try { try {
let verify = await asn.getInfos(fn) const verify = await asn.getInfos(fn)
await asn.insertDB(await dbPromise, verify) await asn.insertDB(await dbPromise, verify)
} catch (e) { } catch (e) {
console.warn('=!= Add to database failed!') console.warn('=!= Add to database failed!')

141
src/external.js Normal file
View 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
}

View File

@ -1,18 +1,11 @@
import path from 'path' import path from 'path'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import asn from './common/async'
import dl from './common/download'
import crypto from 'crypto' import crypto from 'crypto'
import { dbPromise } from './database' import { dbPromise } from './database'
import { addListExternal } from './external'
import { parseStringPromise } from 'xml2js' import { parseStringPromise } from 'xml2js'
const fs = require('fs').promises
const values = require(path.join(process.cwd(), 'values.json')) const values = require(path.join(process.cwd(), 'values.json'))
const memexpire = 1800
let externalTracks = {}
let downloadQueue = []
let downloading = false
function createHash (data) { function createHash (data) {
return crypto return crypto
@ -22,79 +15,6 @@ function createHash (data) {
.substr(0, 8) .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) { async function search (track, limit = 30) {
if (!values.lastfm) return [] if (!values.lastfm) return []
@ -110,9 +30,9 @@ async function search (track, limit = 30) {
return [] return []
} }
let final = [] const final = []
for (let i in data.results.trackmatches.track) { for (const i in data.results.trackmatches.track) {
let res = data.results.trackmatches.track[i] const res = data.results.trackmatches.track[i]
let clean = { let clean = {
id: createHash(res), id: createHash(res),
artist: res.artist, artist: res.artist,
@ -121,13 +41,7 @@ async function search (track, limit = 30) {
mbid: res.mbid mbid: res.mbid
} }
if (externalTracks[clean.id]) { clean = addListExternal(clean.id, clean)
// Copy object
clean = Object.assign({}, externalTracks[clean.id])
} else {
// Save in cache
externalTracks[clean.id] = clean
}
final.push(clean) final.push(clean)
} }
@ -135,42 +49,21 @@ async function search (track, limit = 30) {
return final 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 // Authentication
function getAPISig (params) { function getAPISig (params) {
let allStrings = [] let allStrings = []
let qs = {} const qs = {}
params['api_key'] = values.lastfm.key params.api_key = values.lastfm.key
for (let key in params) { for (const key in params) {
let val = params[key] const val = params[key]
if (val == null || val === '') continue if (val == null || val === '') continue
allStrings.push(key + val) allStrings.push(key + val)
qs[key] = val qs[key] = val
} }
allStrings = allStrings.sort() allStrings = allStrings.sort()
allStrings.push(values.lastfm.secret) 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 return qs
} }
@ -179,18 +72,18 @@ function getAuthURL () {
} }
async function getSession (token) { async function getSession (token) {
let sessSig = getAPISig({ token, method: 'auth.getSession' }) const sessSig = getAPISig({ token, method: 'auth.getSession' })
let res = await rp('http://ws.audioscrobbler.com/2.0/', { qs: sessSig }) const res = await rp('http://ws.audioscrobbler.com/2.0/', { qs: sessSig })
let rep = await parseStringPromise(res) const rep = await parseStringPromise(res)
let name = rep.lfm.session[0].name[0] const name = rep.lfm.session[0].name[0]
let key = rep.lfm.session[0].key[0] const key = rep.lfm.session[0].key[0]
return { name, key } return { name, key }
} }
async function storeSession (userId, session) { async function storeSession (userId, session) {
if (!session.name || !session.key) throw new Error('Invalid session parameter.') if (!session.name || !session.key) throw new Error('Invalid session parameter.')
let db = await dbPromise const db = await dbPromise
let existing = await db.get('SELECT * FROM LastFM WHERE userId = ?', userId) const existing = await db.get('SELECT * FROM LastFM WHERE userId = ?', userId)
if (existing) { if (existing) {
await db.run('UPDATE LastFM SET name = ?, key = ? WHERE userId = ?', session.name, session.key, userId) await db.run('UPDATE LastFM SET name = ?, key = ? WHERE userId = ?', session.name, session.key, userId)
} else { } else {
@ -200,19 +93,19 @@ async function storeSession (userId, session) {
} }
async function disregardSession (userId) { async function disregardSession (userId) {
let db = await dbPromise const db = await dbPromise
return db.run('DELETE FROM LastFM WHERE userId = ?', userId) return db.run('DELETE FROM LastFM WHERE userId = ?', userId)
} }
async function getSessionForUser (userId) { async function getSessionForUser (userId) {
let db = await dbPromise const db = await dbPromise
return db.get('SELECT * FROM LastFM WHERE userId = ?', userId) return db.get('SELECT * FROM LastFM WHERE userId = ?', userId)
} }
async function scrobbleTrack (userId, trackData) { 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.') if (!sess) throw new Error('User does not have a LastFM session.')
let scrobbleSig = getAPISig({ const scrobbleSig = getAPISig({
sk: sess.key, sk: sess.key,
method: 'track.scrobble', method: 'track.scrobble',
artist: trackData.artist || 'Unknown', artist: trackData.artist || 'Unknown',
@ -228,5 +121,13 @@ async function scrobbleTrack (userId, trackData) {
return true return true
} }
export default { search, getTrackMetaReal, invokeDownload, getAPISig, getAuthURL, getSession, export default {
getSessionForUser, storeSession, disregardSession, scrobbleTrack } search,
getAPISig,
getAuthURL,
getSession,
getSessionForUser,
storeSession,
disregardSession,
scrobbleTrack
}

View File

@ -1,34 +1,33 @@
import path from 'path'
import { dbPromise } from './database' import { dbPromise } from './database'
async function getPlaylist (id, order = 'ntry.indx', direction = 'ASC') { async function getPlaylist (id, order = 'ntry.indx', direction = 'ASC') {
let db = await dbPromise const db = await dbPromise
let p = await db.get('SELECT * FROM Playlist WHERE id = ?', id) const p = await db.get('SELECT * FROM Playlist WHERE id = ?', id)
if (!p) throw new Error('No such playlist!') if (!p) throw new Error('No such playlist!')
let q = await db.all('SELECT ntry.id,ntry.trackId,ntry.playlistId,ntry.indx,ntry.userId, \ 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 \ trck.title,trck.artist,trck.genre,trck.year,trck.duration,trck.track,trck.album
FROM PlaylistEntry ntry LEFT JOIN Track \ FROM PlaylistEntry ntry INNER JOIN Track
trck ON ntry.trackId = trck.id WHERE ntry.playlistId = ? ORDER BY ' + order + ' ' + direction.toUpperCase(), id) trck ON ntry.trackId = trck.id WHERE ntry.playlistId = ? ORDER BY ${order} ${direction}`, id)
p.tracks = q || [] p.tracks = q || []
return p return p
} }
async function getPlaylists (userId) { async function getPlaylists (userId) {
let db = await dbPromise const db = await dbPromise
return db.all('SELECT * FROM Playlist WHERE userId = ? OR userId = NULL', userId) return db.all('SELECT * FROM Playlist WHERE userId = ? OR userId = NULL', userId)
} }
async function createPlaylist (userId, title) { async function createPlaylist (userId, title) {
let db = await dbPromise const db = await dbPromise
let alreadyExists = await db.get('SELECT * FROM Playlist WHERE userId = ? AND title = ?', userId, title) 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!') if (alreadyExists) throw new Error('Playlist with the same name already exists!')
await db.run('INSERT INTO Playlist (title,userId) VALUES (?,?)', title, userId) await db.run('INSERT INTO Playlist (title,userId) VALUES (?,?)', title, userId)
return db.get('SELECT id FROM Playlist WHERE title = ? AND userId = ?', title, userId) return db.get('SELECT id FROM Playlist WHERE title = ? AND userId = ?', title, userId)
} }
async function deletePlaylist (userId, playlistId) { async function deletePlaylist (userId, playlistId) {
let db = await dbPromise const db = await dbPromise
let ply = await db.get('SELECT * FROM Playlist WHERE id = ?', playlistId) const ply = await db.get('SELECT * FROM Playlist WHERE id = ?', playlistId)
if (!ply) throw new Error('Could not find playlist specified.') 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.') 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) db.run('DELETE Playlist WHERE id = ?', playlistId)
@ -37,14 +36,14 @@ async function deletePlaylist (userId, playlistId) {
} }
async function addTrack (userId, playlistId, trackId) { async function addTrack (userId, playlistId, trackId) {
let db = await dbPromise const db = await dbPromise
let p = await getPlaylist(playlistId) const p = await getPlaylist(playlistId)
if (!p) throw new Error('Invalid playlist.') if (!p) throw new Error('Invalid playlist.')
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.') if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.')
let alreadyExists = false let alreadyExists = false
for (let i in p.tracks) { for (const i in p.tracks) {
let tr = p.tracks[i] const tr = p.tracks[i]
if (tr.trackId === parseInt(trackId)) { if (tr.trackId === parseInt(trackId)) {
alreadyExists = true alreadyExists = true
break break
@ -57,29 +56,29 @@ async function addTrack (userId, playlistId, trackId) {
} }
async function removeTrack (userId, playlistId, trackId) { async function removeTrack (userId, playlistId, trackId) {
let db = await dbPromise const db = await dbPromise
let p = await getPlaylist(playlistId) const p = await getPlaylist(playlistId)
if (!p) throw new Error('Invalid playlist.') if (!p) throw new Error('Invalid playlist.')
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.') 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.') if (!trackEntry) throw new Error('This track is not in the playlist.')
return db.run('DELETE FROM PlaylistEntry WHERE id = ?', trackEntry.id) return db.run('DELETE FROM PlaylistEntry WHERE id = ?', trackEntry.id)
} }
async function moveTrack (userId, playlistId, trackId, position) { async function moveTrack (userId, playlistId, trackId, position) {
let db = await dbPromise const db = await dbPromise
let p = await getPlaylist(playlistId) const p = await getPlaylist(playlistId)
if (!p) throw new Error('Invalid playlist.') if (!p) throw new Error('Invalid playlist.')
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.') 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.') if (!trackEntry) throw new Error('This track is not in the playlist.')
let trcksNew = [] const trcksNew = []
for (let i in p.tracks) { for (const i in p.tracks) {
let trck = p.tracks[i] const trck = p.tracks[i]
if (trck.trackId === trackId) { if (trck.trackId === trackId) {
trck.indx = position trck.indx = position
continue continue
@ -91,8 +90,8 @@ async function moveTrack (userId, playlistId, trackId, position) {
} }
// Update indexes // Update indexes
for (let i in trcksNew) { for (const i in trcksNew) {
let trck = trcksNew[i] const trck = trcksNew[i]
await db.run('UPDATE PlaylistEntry SET indx = ? WHERE trackId = ? AND playlistId = ?', trck.indx, trck.trackId, playlistId) await db.run('UPDATE PlaylistEntry SET indx = ? WHERE trackId = ? AND playlistId = ?', trck.indx, trck.trackId, playlistId)
} }

View File

@ -5,7 +5,6 @@ import bodyParser from 'body-parser'
import connectSession from 'connect-redis' import connectSession from 'connect-redis'
import redis from 'redis' import redis from 'redis'
import http from 'http' import http from 'http'
import https from 'https'
import ffmpeg from 'fluent-ffmpeg' import ffmpeg from 'fluent-ffmpeg'
import { user, userMiddleware } from './user' import { user, userMiddleware } from './user'
import { dbPromise } from './database' import { dbPromise } from './database'
@ -14,6 +13,7 @@ import asn from './common/async'
import playlist from './playlist' import playlist from './playlist'
import lastfm from './lastfm' import lastfm from './lastfm'
import external from './external.js'
require('express-async-errors') require('express-async-errors')
@ -40,7 +40,7 @@ const router = express.Router()
const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year'] const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year']
const srchcategories = ['title', 'artist', 'album'] const srchcategories = ['title', 'artist', 'album']
let SessionStore = connectSession(session) const SessionStore = connectSession(session)
app.use(session({ app.use(session({
key: values.session_key || 'Session', key: values.session_key || 'Session',
secret: values.session_secret || 'ch4ng3 m3!', secret: values.session_secret || 'ch4ng3 m3!',
@ -73,17 +73,17 @@ router.get('/tracks', userMiddleware, async (req, res) => {
sortdir = 'asc' sortdir = 'asc'
} }
let db = await dbPromise const db = await dbPromise
let count = (await db.get('SELECT COUNT(*) FROM Track'))['COUNT(*)'] 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 if (page > pageCount) page = pageCount
let offset = (page - 1) * tracksPerPage const offset = (page - 1) * tracksPerPage
let tracks = await db.all(`SELECT * FROM Track ORDER BY ${sort} ${sortdir.toUpperCase()} LIMIT ? OFFSET ?`, tracksPerPage, offset) 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 delete tracks[i].file
} }
@ -93,13 +93,21 @@ router.get('/tracks', userMiddleware, async (req, res) => {
}) })
router.get('/tracks/search', userMiddleware, async (req, res) => { router.get('/tracks/search', userMiddleware, async (req, res) => {
const streamable = (req.query.streamable === '1')
let query = req.query.q let query = req.query.q
let streamable = (req.query.streamable === '1')
let qr = '' let qr = ''
let exact = false 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) { if (query.indexOf(':') !== -1) {
let ctr = query.split(':') const ctr = query.split(':')
if (srchcategories.indexOf(ctr[0]) !== -1) { if (srchcategories.indexOf(ctr[0]) !== -1) {
qr = `ifnull(${ctr[0]}, '')` qr = `ifnull(${ctr[0]}, '')`
@ -108,8 +116,8 @@ router.get('/tracks/search', userMiddleware, async (req, res) => {
} }
if (qr === '') { if (qr === '') {
for (let c in srchcategories) { for (const c in srchcategories) {
let cat = srchcategories[c] const cat = srchcategories[c]
if (parseInt(c) !== 0) qr += ' || ' if (parseInt(c) !== 0) qr += ' || '
qr += `ifnull(${cat}, '')` qr += `ifnull(${cat}, '')`
} }
@ -120,7 +128,7 @@ router.get('/tracks/search', userMiddleware, async (req, res) => {
exact = true exact = true
} }
let original = String(query) const original = String(query)
if (!exact) query = `%${query}%` if (!exact) query = `%${query}%`
let sort = req.query.sort let sort = req.query.sort
@ -139,31 +147,31 @@ router.get('/tracks/search', userMiddleware, async (req, res) => {
page = 1 page = 1
} }
let db = await dbPromise const db = await dbPromise
let count = (await db.get(`SELECT COUNT(*) FROM Track WHERE ${qr} LIKE ?`, query))['COUNT(*)'] let count = (await db.get(`SELECT COUNT(*) as 'count' FROM Track WHERE ${qr} LIKE ?`, query)).count
let pageCount = Math.ceil(count / tracksPerPage) let pageCount = Math.ceil(count / tracksPerPage)
if (page > pageCount) page = pageCount if (page > pageCount) page = pageCount
let offset = (page - 1) * tracksPerPage const offset = (page - 1) * tracksPerPage
let tracks = await db.all(`SELECT * FROM Track WHERE ${qr} LIKE ? ORDER BY ${sort} ${sortdir.toUpperCase()} LIMIT ? OFFSET ?`, let tracks = await db.all(`SELECT * FROM Track WHERE ${qr} LIKE ? ORDER BY ${sort} ${sortdir} LIMIT ? OFFSET ?`,
query, tracksPerPage, offset) query, tracksPerPage, offset)
let llimit = tracksPerPage - count let llimit = tracksPerPage - count
if (streamable && page === pageCount && llimit > 1) { if (streamable && page === pageCount && llimit > 1) {
if (llimit < 10) llimit = 10 if (llimit < 10) llimit = 10
try { try {
let lfm = await lastfm.search(original, llimit) const lfm = await lastfm.search(original, llimit)
if (lfm && lfm.length) { if (lfm && lfm.length) {
tracks = tracks.concat(lfm) tracks = tracks.concat(lfm)
count = count + lfm.length count = count + lfm.length
if (page == 0) page = 1 if (page === 0) page = 1
if (pageCount == 0) pageCount = 1 if (pageCount === 0) pageCount = 1
} }
} catch (e) {} } catch (e) {}
} }
for (let i in tracks) { for (const i in tracks) {
delete tracks[i].file 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) => { 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) let track = await db.get('SELECT * FROM Track WHERE id = ?', id)
if (!track) { if (!track) {
track = await lastfm.getTrackMetaReal(id) track = await external.getTrackMetaReal(id)
if (!track) throw new Error('404 track not found') 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) => { router.post('/track/:id', userMiddleware, async (req, res, next) => {
let id = req.params.id const id = req.params.id
let meta = req.body const meta = req.body
let db = await dbPromise const db = await dbPromise
let track = await db.get('SELECT file FROM Track WHERE id = ?', id) const track = await db.get('SELECT file FROM Track WHERE id = ?', id)
if (!track) throw new Error('404 track not found') 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) await asn.updateDB(db, id, m.dbq)
res.jsonp(m) 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) => { router.post('/playlist/new', userMiddleware, async (req, res, next) => {
if (!req.body.title) throw new Error('Title missing from body.') if (!req.body.title) throw new Error('Title missing from body.')
let id = await playlist.createPlaylist(req.session.user, req.body.title) const id = await playlist.createPlaylist(req.session.user, req.body.title)
res.jsonp({success: true, playlist: id.id}) res.jsonp({ success: true, playlist: id.id })
}) })
router.post('/playlist/delete/:playlistId', userMiddleware, async (req, res, next) => { 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) await playlist.deletePlaylist(req.session.user, pId)
res.jsonp({success: true}) res.jsonp({ success: true })
}) })
// Playlist track endpoints // Playlist track endpoints
router.post('/playlist/track/put/:playlistId/:trackId', userMiddleware, async (req, res, next) => { router.post('/playlist/track/put/:playlistId/:trackId', userMiddleware, async (req, res, next) => {
let pId = req.params.playlistId const pId = req.params.playlistId
let tId = req.params.trackId const tId = req.params.trackId
await playlist.addTrack(req.session.user, pId, tId) 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) => { router.post('/playlist/track/remove/:playlistId/:trackId', userMiddleware, async (req, res, next) => {
let pId = req.params.playlistId const pId = req.params.playlistId
let tId = req.params.trackId const tId = req.params.trackId
await playlist.removeTrack(req.session.user, pId, tId) 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) => { router.post('/playlist/track/move/:playlistId/:trackId', userMiddleware, async (req, res, next) => {
let pId = req.params.playlistId const pId = req.params.playlistId
let tId = req.params.trackId const tId = req.params.trackId
let pos = parseInt(req.body.position) const pos = parseInt(req.body.position)
if (!pos || isNaN(pos)) throw new Error('Invalid position.') if (!pos || isNaN(pos)) throw new Error('Invalid position.')
await playlist.moveTrack(req.session.user, pId, tId, pos) 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) => { 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) => { 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 }) if (sess) return res.jsonp({ connected: true, name: sess.name })
res.jsonp({ connected: false }) 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) => { 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!') 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) await lastfm.storeSession(req.session.user, session)
res.redirect('/?success=lastfm') res.redirect('/?success=lastfm')
}) })
router.post('/lastfm/scrobble/:track', userMiddleware, async (req, res, next) => { router.post('/lastfm/scrobble/:track', userMiddleware, async (req, res, next) => {
let id = req.params.track const id = req.params.track
let user = req.session.user const user = req.session.user
let db = await dbPromise const db = await dbPromise
let track = await db.get('SELECT title,artist,album,duration FROM Track WHERE id = ?', id) let track = await db.get('SELECT title,artist,album,duration FROM Track WHERE id = ?', id)
if (!track) { if (!track) {
track = await lastfm.getTrackMetaReal(id) track = await external.getTrackMetaReal(id)
if (!track) throw new Error('404 file not found') if (!track) throw new Error('404 file not found')
} }
await scrobble(user, track) 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) => { router.get('/serve/by-id/:id', userMiddleware, async (req, res, next) => {
let id = req.params.id const id = req.params.id
let dl = (req.query.dl === '1') const dl = (req.query.dl === '1')
let db = await dbPromise const db = await dbPromise
let track = await db.get('SELECT file FROM Track WHERE id = ?', id) let track = await db.get('SELECT file FROM Track WHERE id = ?', id)
if (!track) { if (!track) {
track = await lastfm.getTrackMetaReal(id) track = await external.getTrackMetaReal(id)
if (!track) throw new Error('404 file not found') if (!track) throw new Error('404 file not found')
if (dl) { if (dl) {
lastfm.invokeDownload(id) external.invokeDownload(id)
return res.end('<p>OK</p><script>window.close();</script>') 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) return ffmpeg(track.file)
.audioCodec('libmp3lame') .audioCodec('libmp3lame')
.format('mp3') .format('mp3')
.on('error', (e) => console.error(e)) .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') res.set('Cache-Control', 'public, max-age=31557600')
if (dl) return res.download(fpath) 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) => { router.use((err, req, res, next) => {
let msg = err.message const msg = err.message
dev && console.error(err.stack) dev && console.error(err.stack)
res.status(msg.indexOf('404') !== -1 ? 404 : 400).jsonp({ error: err.message, stack: dev ? err.stack.toString() : undefined }) res.status(msg.indexOf('404') !== -1 ? 404 : 400).jsonp({ error: err.message, stack: dev ? err.stack.toString() : undefined })
}) })

View File

@ -7,8 +7,8 @@ import { dbPromise } from './database'
const router = express.Router() const router = express.Router()
async function userInfoPublic (id) { async function userInfoPublic (id) {
let db = await dbPromise const db = await dbPromise
let u = await db.get('SELECT id, username, image FROM User WHERE id = ?', id) const u = await db.get('SELECT id, username, image FROM User WHERE id = ?', id)
if (!u) return {} if (!u) return {}
return { return {
id: u.id, id: u.id,
@ -18,7 +18,7 @@ async function userInfoPublic (id) {
} }
export async function userInfo (id) { export async function userInfo (id) {
let db = await dbPromise const db = await dbPromise
return db.get('SELECT * FROM User WHERE id = ?', id) return db.get('SELECT * FROM User WHERE id = ?', id)
} }
@ -39,11 +39,11 @@ export function user (oauth, registrations) {
if (!oauth) return router 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) => { router.get('/login/oauth/_redirect', async (req, res) => {
let code = req.query.code const code = req.query.code
let state = req.query.state const state = req.query.state
if (!code || !state) throw new Error('Something went wrong!') 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.') 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!') throw new Error('No authorization!')
} }
let accessToken = tokens[2].access_token const accessToken = tokens[2].access_token
// Get user information on remote // Get user information on remote
let userInfo let userInfo
@ -70,8 +70,8 @@ export function user (oauth, registrations) {
if (!userInfo) throw new Error('Couldn\'t get user information!') if (!userInfo) throw new Error('Couldn\'t get user information!')
// Let's see if there's a link for this user already.. // Let's see if there's a link for this user already..
let db = await dbPromise const db = await dbPromise
let userLocal = await db.get('SELECT * FROM OAuth WHERE remoteId = ?', userInfo.id) const userLocal = await db.get('SELECT * FROM OAuth WHERE remoteId = ?', userInfo.id)
// User and link both exist // User and link both exist
if (userLocal) { if (userLocal) {
@ -92,7 +92,7 @@ export function user (oauth, registrations) {
// Create a new user and log in // 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()) 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!') if (!newU) throw new Error('Something went wrong!')
@ -103,27 +103,27 @@ export function user (oauth, registrations) {
}) })
router.get('/login/oauth', async (req, res) => { 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 req.session.oauthState = state
return res.redirect(oauth2.getAuthorizeUrl({ return res.redirect(oauth2.getAuthorizeUrl({
'redirect_uri': oauth.redirectUri, redirect_uri: oauth.redirectUri,
'scope': oauth.scope, scope: oauth.scope,
'response_type': 'code', response_type: 'code',
'state': state state: state
})) }))
}) })
router.use('/login', async (req, res, next) => { router.use('/login', async (req, res, next) => {
if (req.session && req.session.user) return res.redirect('/') if (req.session && req.session.user) return res.redirect('/')
let header = req.get('authorization') || '' const header = req.get('authorization') || ''
let token = header.split(/\s+/).pop() || '' const token = header.split(/\s+/).pop() || ''
let auth = Buffer.from(token, 'base64').toString() const auth = Buffer.from(token, 'base64').toString()
let parts = auth.split(/:/) const parts = auth.split(/:/)
let username = parts[0] const username = parts[0]
let password = parts[1] 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 req.message = message
if ((!username || !password) && (username !== 'oauth' && oauth)) { if ((!username || !password) && (username !== 'oauth' && oauth)) {
@ -134,8 +134,8 @@ export function user (oauth, registrations) {
return res.redirect('/user/login/oauth') return res.redirect('/user/login/oauth')
} }
let db = await dbPromise const db = await dbPromise
let user = await db.get('SELECT * FROM User WHERE username = ?', username) const user = await db.get('SELECT * FROM User WHERE username = ?', username)
if (!user) return next() if (!user) return next()
if (!user.password && oauth) { if (!user.password && oauth) {
@ -143,7 +143,7 @@ export function user (oauth, registrations) {
} }
// Compare passwords // Compare passwords
let ures = await bcrypt.compare(password, user.password) const ures = await bcrypt.compare(password, user.password)
if (!ures) return next() if (!ures) return next()
// Set login success // Set login success