first version

This commit is contained in:
Jay 2019-08-13 07:39:49 +08:00
commit 1fda2ff1f6
14 changed files with 2590 additions and 0 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
node_modules

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
public/
downloaded/

9
Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM node:lts-alpine
WORKDIR /data
COPY . .
RUN apk add --no-cache ffmpeg python python3 curl wget \
&& curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl \
&& chmod a+rx /usr/local/bin/youtube-dl \
&& npm install --prod
EXPOSE 10230
CMD ["npm", "run", "start"]

4
config/index.js Normal file
View File

@ -0,0 +1,4 @@
const path = require('path')
module.exports = {
download_location: process.env.DL_LOC || path.resolve(__dirname, '..', 'downloaded')
}

9
index.js Normal file
View File

@ -0,0 +1,9 @@
const fs = require('fs')
const config = require('./config/index.js')
require('module-alias/register')
if (!fs.existsSync(config.download_location)) {
fs.mkdirSync(config.download_location)
}
require('./server')

9
jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@libs/*": ["libs/*"],
"@config/*": ["config/*"]
}
}
}

36
libs/memstore.js Normal file
View File

@ -0,0 +1,36 @@
const store = {}
const storeFunc = {
/**
* set store key vlaue
* @param {string} key
* @param {object} val
* @return {boolean}
*/
set: (key, val) => {
if (typeof key !== 'string' || key.length === 0) return false
store[key] = val
return true
},
/**
* get store key value
* @param {string} key
* @return {object?}
*/
get: (key) => {
if (typeof key !== 'string' || key.length === 0) return null
return store[key] || null
},
/**
* del store key vlaue
* @param {string} key
* @return {boolean}
*/
del: (key) => {
if (typeof key !== 'string' || key.length === 0) return false
delete store[key]
return true
}
}
module.exports = storeFunc

40
libs/youtube.js Normal file
View File

@ -0,0 +1,40 @@
const exec = require('child_process').exec
const command = 'youtube-dl'
const cmdOpts = ['--id', '-x', '--audio-format', 'mp3']
const config = require('@config/index.js')
const cwd = config.download_location
/**
* download youtube video
* @param {string} url
* @return {boolean}
*/
module.exports.download = async (url = '') => {
if (typeof url !== 'string' || url.trim().length === 0) return false
const result = await new Promise(resolve => {
exec(`${command} ${cmdOpts.join(' ')} "${url}"`, { cwd }, (err, sout, serr) => {
if (err) return resolve(false)
return resolve(true)
})
})
return result
}
/**
* get youtube video title
* @param {string} url
* @return {string}
*/
module.exports.getTitle = async (url = '') => {
if (typeof url !== 'string' || url.trim().length === 0) return ''
const result = await new Promise(resolve => {
exec(`${command} -e "${url}"`, { cwd }, (err, sout, serr) => {
if (err) return resolve('')
resolve(sout)
})
})
return result
}

2344
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "ytdl",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"check": "standard --fix --verbose",
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@koa/cors": "^3.0.0",
"@koa/router": "^8.0.0",
"koa": "^2.7.0",
"koa-body": "^4.1.0",
"koa-logger": "^3.2.1",
"koa-mount": "^4.0.0",
"koa-static": "^5.0.0",
"module-alias": "^2.2.1"
},
"devDependencies": {
"standard": "^13.1.0"
},
"_moduleAliases": {
"@root": ".",
"@libs": "libs",
"@config": "config"
}
}

10
route/api/index.js Normal file
View File

@ -0,0 +1,10 @@
const Router = require('@koa/router')
const r = new Router()
module.exports = r
r.get('/health', async c => {
c.status = 200
c.body = 'ok'
})
r.use('/youtube', require('./youtube/index.js').routes())

View File

@ -0,0 +1,61 @@
const Router = require('@koa/router')
const r = new Router()
module.exports = r
const koaBody = require('koa-body')
const {
download: ytdl,
getTitle
} = require('@libs/youtube.js')
const qs = require('querystring')
const fs = require('fs')
const path = require('path')
const config = require('@config/index.js')
const store = require('@libs/memstore.js')
r.post('/download', koaBody(), async c => {
console.log(c.request)
const { url } = c.request.body
if (typeof url !== 'string' || url.trim().length === 0) c.throw(400, 'url is empty')
let id = ''
if (/:\/\/youtu\.be/i.test(url)) {
const arr = url.split('/')
id = arr[arr.length - 1]
} else {
const arr = url.split('?')
if (arr.length !== 2) c.throw(400, 'url format error')
const q = qs.parse(arr[1])
if (!('v' in q) || typeof q.v !== 'string' || q.v.length === 0) c.throw(400, 'url format error')
id = q.v
}
const title = await getTitle(url)
const result = await ytdl(url)
if (!result) c.throw(500, 'download fail')
store.set(`${id}.mp3`, { title })
c.body = {
url: `${c.protocol}://${c.host.replace(/\/$/, '')}/api/youtube/download?file=${id}.mp3`
}
c.status = 200
})
r.get('/download', async c => {
const { file } = c.query
if (typeof file !== 'string' || file.trim().length === 0) c.throw(400, 'file is empty')
const fp = path.resolve(config.download_location, file)
if (!fs.existsSync(fp)) c.throw(404, 'file not found')
const fileInfo = store.get(file)
let fn = file
if (fileInfo !== null && 'title' in fileInfo) fn = fileInfo.title
const rs = fs.createReadStream(fp)
c.set('Content-disposition', `attachment; filename="${encodeURIComponent(fn)}"; filename*="utf8''${encodeURIComponent(fn)}";`)
c.set('Content-Type', 'audio/mpeg')
c.body = rs
c.status = 200
})

10
route/index.js Normal file
View File

@ -0,0 +1,10 @@
const Router = require('@koa/router')
const r = new Router()
module.exports = r
r.get('/', async c => {
c.body = 'ok'
c.status = 200
})
r.use('/api', require('./api/index.js').routes())

22
server.js Normal file
View File

@ -0,0 +1,22 @@
const Koa = require('koa')
const app = new Koa()
const koaLogger = require('koa-logger')
const cors = require('@koa/cors')
const koaStatic = require('koa-static')
const rootRoouter = require('./route/index.js')
const path = require('path')
const server = app.listen(10230, () => {
console.log(`server start on port ${server.address().port}`)
})
module.exports = server
app.use(koaLogger())
app.use(cors({
origin: ctx => ctx.get('origin'),
credentials: true
}))
app.use(koaStatic(path.resolve(__dirname, 'public')))
app.use(rootRoouter.allowedMethods())
app.use(rootRoouter.routes())