├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── app.js ├── bin └── www ├── package.json ├── public ├── images │ └── favicon.png └── stylesheets │ └── style.css ├── routes ├── index.js └── users.js └── views ├── error.ejs └── index.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kieran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node ./bin/www -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imoocDownload 2 | ###慕课网视频下载小程序 3 | node + express 4 | 利用慕课网的api 生成视频下载链接 5 | 然后在前台显示出来 6 | ###Start 7 | ``` 8 | $ node ./bin/www 9 | 然后浏览器打开 localhost:5000 10 | ``` 11 | 正常情况 右击 链接点 链接另存为 就能在浏览器里下载了 12 | 这样下载能保持文件名正常 13 | 如果用迅雷,文件名会变成 H.mp4 需要手动改文件名 14 | 不过用迅雷可以批量下载,右击链接,选择 迅雷批量下载链接 就行了。 15 | 16 | PS: routes/index.js 里的cookie需要改成你的慕课网的cookie 17 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'ejs'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(__dirname + '/public/favicon.ico')); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', routes); 26 | app.use('/users', users); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handlers 36 | 37 | // development error handler 38 | // will print stacktrace 39 | if (app.get('env') === 'development') { 40 | app.use(function(err, req, res, next) { 41 | res.status(err.status || 500); 42 | res.render('error', { 43 | message: err.message, 44 | error: err 45 | }); 46 | }); 47 | } 48 | 49 | // production error handler 50 | // no stacktraces leaked to user 51 | app.use(function(err, req, res, next) { 52 | res.status(err.status || 500); 53 | res.render('error', { 54 | message: err.message, 55 | error: {} 56 | }); 57 | }); 58 | 59 | 60 | module.exports = app; 61 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('imoocVideoDowload:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '5000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imoocVideoDowload", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.12.4", 10 | "cookie-parser": "~1.3.5", 11 | "debug": "~2.2.0", 12 | "ejs": "~2.3.1", 13 | "express": "~4.12.4", 14 | "morgan": "~1.5.3", 15 | "serve-favicon": "~2.2.1", 16 | "cheerio": "^0.17.0", 17 | "superagent": "^0.20.0", 18 | "eventproxy": "^0.3.1" 19 | } 20 | } -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperKieran/imoocDownload/59577ebba47f12f0424aee1ad32fdcfad482e487/public/images/favicon.png -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | .jumbotron.masthead{ 2 | margin-top: 20px; 3 | } 4 | .contentLeft, .contentRight{ 5 | float: left; 6 | } 7 | 8 | .highlight { 9 | color: #c33; 10 | } 11 | .course .btn.btn-warning.btn-xs{ 12 | margin-left: 20px; 13 | } 14 | .course .content { 15 | line-height: 22px; 16 | margin-top: 20px; 17 | } 18 | .course .thumbnail { 19 | width: 200px; 20 | overflow: hidden; 21 | padding: 0; 22 | } 23 | .course .thumbnail img { 24 | width: 210px; 25 | overflow: hidden; 26 | margin: 0; 27 | padding: 0; 28 | transition-duration: 1s; 29 | -moz-transition-duration: 1s; 30 | -webkit-transition-duration: 1s; 31 | -o-transition-duration: 1s; 32 | } 33 | .course .thumbnail, .search-course .course .introduction { 34 | float: left; 35 | height: 120px; 36 | margin-right: 10px; 37 | } 38 | .course .thumbnail img { 39 | min-width: 200px; 40 | height: 120px; 41 | border: 0; 42 | } 43 | .course .thumbnail:hover img { 44 | transform: scale(1.2); 45 | } 46 | .course .introduction { 47 | margin-left: 10px; 48 | margin-top: -4px; 49 | font-size: 12px; 50 | line-height: 24px; 51 | } 52 | .autowrap { 53 | word-wrap: break-word; 54 | word-break: break-all; 55 | } 56 | /* Loading Start */ 57 | .cover { 58 | position:fixed; 59 | top: 0px; 60 | right:0px; 61 | bottom:0px; 62 | filter: alpha(opacity=60); 63 | background-color: #777; 64 | z-index: 1002; 65 | left: 0px; 66 | display:none; 67 | opacity:0.5; 68 | -moz-opacity:0.5; 69 | } 70 | .sk-spinner-double-bounce.sk-spinner { 71 | width: 40px; 72 | height: 40px; 73 | position: relative; 74 | margin: 0 auto; 75 | display: none; 76 | } 77 | .sk-spinner-double-bounce .sk-double-bounce1, .sk-spinner-double-bounce .sk-double-bounce2 { 78 | width: 100%; 79 | height: 100%; 80 | border-radius: 50%; 81 | background-color: #333; 82 | opacity: 0.6; 83 | position: absolute; 84 | top: 0; 85 | left: 0; 86 | -webkit-animation: sk-doubleBounce 2s infinite ease-in-out; 87 | animation: sk-doubleBounce 2s infinite ease-in-out; } 88 | .sk-spinner-double-bounce .sk-double-bounce2 { 89 | -webkit-animation-delay: -1s; 90 | animation-delay: -1s; } 91 | 92 | @-webkit-keyframes sk-doubleBounce { 93 | 0%, 100% { 94 | -webkit-transform: scale(0); 95 | transform: scale(0); } 96 | 97 | 50% { 98 | -webkit-transform: scale(1); 99 | transform: scale(1); } } 100 | 101 | @keyframes sk-doubleBounce { 102 | 0%, 100% { 103 | -webkit-transform: scale(0); 104 | transform: scale(0); } 105 | 106 | 50% { 107 | -webkit-transform: scale(1); 108 | transform: scale(1); } } 109 | /* Loading End */ -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var cheerio = require('cheerio'); 3 | var eventproxy = require('eventproxy'); 4 | var superagent = require('superagent'); 5 | var url = require('url'); 6 | var router = express.Router(); 7 | 8 | /* GET home page. */ 9 | router.get('/', function(req, res, next) { 10 | res.render('index'); 11 | }); 12 | 13 | router.post('/search', function(req, res, next){ 14 | superagent.get('http://www.imooc.com/index/search?words='+req.body.keyword) 15 | .end(function(err, sres) { 16 | if (err) { 17 | return next(err); 18 | } 19 | 20 | var $ = cheerio.load(sres.text); 21 | var ulList = $('#main .search-main ul li.course-item'); 22 | var courseItem = ''; 23 | 24 | for(var i=0;i<$(ulList).length;i++){ 25 | courseItem += '
<%= error.stack %>4 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |