first version
This commit is contained in:
commit
1fda2ff1f6
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
public/
|
||||
downloaded/
|
9
Dockerfile
Normal file
9
Dockerfile
Normal 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
4
config/index.js
Normal 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
9
index.js
Normal 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
9
jsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@libs/*": ["libs/*"],
|
||||
"@config/*": ["config/*"]
|
||||
}
|
||||
}
|
||||
}
|
36
libs/memstore.js
Normal file
36
libs/memstore.js
Normal 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
40
libs/youtube.js
Normal 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
2344
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal 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
10
route/api/index.js
Normal 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())
|
61
route/api/youtube/index.js
Normal file
61
route/api/youtube/index.js
Normal 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
10
route/index.js
Normal 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
22
server.js
Normal 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())
|
Loading…
Reference in New Issue
Block a user