init
This commit is contained in:
commit
a3cf0aa893
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
.idea
|
||||||
|
lib-cov
|
||||||
|
coverage
|
||||||
|
.grunt
|
||||||
|
.lock-wscript
|
||||||
|
build/Release
|
||||||
|
node_modules
|
22
LICENSE.md
Normal file
22
LICENSE.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 Ahmed Ibrhim
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
## Website Downloader
|
||||||
|
Download the complete source code of any website (including all assets)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Website downloader works with `wget` and `archiver` to download all websites assets and compress then sends it back to the user through socket channel
|
||||||
|
|
||||||
|
**wget params the being used**
|
||||||
|
|
||||||
|
`wget --mirror --convert-links --adjust-extension --page-requisites
|
||||||
|
--no-parent http://example.org`
|
||||||
|
|
||||||
|
**Explanation of the various flags:**
|
||||||
|
|
||||||
|
- --mirror – Makes (among other things) the download recursive.
|
||||||
|
- --convert-links – convert all the links (also to stuff like CSS stylesheets) to relative, so it will be suitable for offline viewing.
|
||||||
|
- --adjust-extension – Adds suitable extensions to filenames (html or css) depending on their content-type.
|
||||||
|
- --page-requisites – Download things like CSS style-sheets and images required to properly display the page offline.
|
||||||
|
- --no-parent – When recursing do not ascend to the parent directory. It useful for restricting the download to only a portion of the site
|
||||||
|
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
- `$ npm install`
|
||||||
|
- `$ npm start`
|
||||||
|
- `http://localhost:3000/`
|
||||||
|
|
41
app.js
Normal file
41
app.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
var createError = require('http-errors');
|
||||||
|
var express = require('express');
|
||||||
|
var path = require('path');
|
||||||
|
var cookieParser = require('cookie-parser');
|
||||||
|
var logger = require('morgan');
|
||||||
|
|
||||||
|
var indexRouter = require('./routes/index');
|
||||||
|
var usersRouter = require('./routes/users');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
// view engine setup
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
app.set('view engine', 'hbs');
|
||||||
|
|
||||||
|
app.use(logger('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
app.use('/', indexRouter);
|
||||||
|
app.use('/users', usersRouter);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
res.render('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
3
app.json
Normal file
3
app.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"stack": "heroku-22"
|
||||||
|
}
|
53
archiver/index.js
Normal file
53
archiver/index.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
var archiver = require("archiver");
|
||||||
|
var fs = require("fs");
|
||||||
|
|
||||||
|
module.exports = (file, io, data) => {
|
||||||
|
console.log("--------- file:", file);
|
||||||
|
var output = fs.createWriteStream("./public/sites/" + file + ".zip");
|
||||||
|
var archive = archiver("zip", {
|
||||||
|
zlib: { level: 9 }, // Sets the compression level.
|
||||||
|
});
|
||||||
|
|
||||||
|
// listen for all archive data to be written
|
||||||
|
// 'close' event is fired only when a file descriptor is involved
|
||||||
|
output.on("close", function () {
|
||||||
|
console.log(archive.pointer() + " total bytes");
|
||||||
|
console.log(
|
||||||
|
"archiver has been finalized and the output file descriptor has closed."
|
||||||
|
);
|
||||||
|
io.emit(data.token, { progress: "Completed", file });
|
||||||
|
});
|
||||||
|
|
||||||
|
// This event is fired when the data source is drained no matter what was the data source.
|
||||||
|
// It is not part of this library but rather from the NodeJS Stream API.
|
||||||
|
// @see: https://nodejs.org/api/stream.html#stream_event_end
|
||||||
|
output.on("end", function () {
|
||||||
|
console.log("Data has been drained");
|
||||||
|
});
|
||||||
|
|
||||||
|
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
||||||
|
archive.on("warning", function (err) {
|
||||||
|
if (err.code === "ENOENT") {
|
||||||
|
// log warning
|
||||||
|
} else {
|
||||||
|
// throw error
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// good practice to catch this error explicitly
|
||||||
|
archive.on("error", function (err) {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
// pipe archive data to the file
|
||||||
|
archive.pipe(output);
|
||||||
|
|
||||||
|
// append files from a sub-directory and naming it `new-subdir` within the archive
|
||||||
|
|
||||||
|
archive.directory("./" + file, false);
|
||||||
|
|
||||||
|
// finalize the archive (ie we are done appending files but streams have to finish yet)
|
||||||
|
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
|
||||||
|
archive.finalize();
|
||||||
|
};
|
90
bin/www
Normal file
90
bin/www
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var app = require('../app');
|
||||||
|
var debug = require('debug')('website-downloader:server');
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get port from environment and store in Express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '3002');
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var server = http.createServer(app);
|
||||||
|
var io = require('socket.io')(server);
|
||||||
|
|
||||||
|
// Pass socket Object to it's modula
|
||||||
|
require('../socket/socket')(io)
|
||||||
|
/**
|
||||||
|
* Listen on provided port, on all network interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a port into a number, string, or false.
|
||||||
|
*/
|
||||||
|
function normalizePort(val) {
|
||||||
|
var port = parseInt(val, 10);
|
||||||
|
|
||||||
|
if (isNaN(port)) {
|
||||||
|
// named pipe
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port >= 0) {
|
||||||
|
// port number
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "error" event.
|
||||||
|
*/
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bind = typeof port === 'string'
|
||||||
|
? 'Pipe ' + port
|
||||||
|
: 'Port ' + port;
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(bind + ' requires elevated privileges');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(bind + ' is already in use');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "listening" event.
|
||||||
|
*/
|
||||||
|
function onListening() {
|
||||||
|
var addr = server.address();
|
||||||
|
var bind = typeof addr === 'string'
|
||||||
|
? 'pipe ' + addr
|
||||||
|
: 'port ' + addr.port;
|
||||||
|
debug('Listening on ' + bind);
|
||||||
|
}
|
1662
package-lock.json
generated
Normal file
1662
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "website-downloader",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./bin/www",
|
||||||
|
"dev": "export NODE_ENV=Development && npm start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"archiver": "^3.1.1",
|
||||||
|
"cookie-parser": "~1.4.4",
|
||||||
|
"debug": "~2.6.9",
|
||||||
|
"express": "~4.17.3",
|
||||||
|
"hbs": "~4.2.0",
|
||||||
|
"http-errors": "~1.6.3",
|
||||||
|
"jszip": "^3.8.0",
|
||||||
|
"morgan": "~1.9.1",
|
||||||
|
"socket.io": "^2.5.0",
|
||||||
|
"socket.io-client": "^2.5.0"
|
||||||
|
}
|
||||||
|
}
|
9
public/stylesheets/bootstrap.min.css
vendored
Normal file
9
public/stylesheets/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
199
public/stylesheets/style.css
Normal file
199
public/stylesheets/style.css
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
header [role="progressbar"][aria-busy="true"] {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #159756;
|
||||||
|
-webkit-animation: preloader-background linear 3.5s infinite;
|
||||||
|
animation: preloader-background linear 3.5s infinite;
|
||||||
|
}
|
||||||
|
header [role="progressbar"][aria-busy="true"]::before, header [role="progressbar"][aria-busy="true"]::after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
width: 0;
|
||||||
|
height: 8px;
|
||||||
|
background: #afa;
|
||||||
|
-webkit-animation: preloader-front linear 3.5s infinite;
|
||||||
|
animation: preloader-front linear 3.5s infinite;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
header [role="progressbar"][aria-busy="true"]::before {
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
header [role="progressbar"][aria-busy="true"]::after {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes preloader-background {
|
||||||
|
0%, 24.9% {
|
||||||
|
background-color: #159756;
|
||||||
|
}
|
||||||
|
25%, 49.9% {
|
||||||
|
background-color: #da4733;
|
||||||
|
}
|
||||||
|
50%, 74.9% {
|
||||||
|
background-color: #3b78e7;
|
||||||
|
}
|
||||||
|
75%, 100% {
|
||||||
|
background-color: #fdba2c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes preloader-background {
|
||||||
|
0%, 24.9% {
|
||||||
|
background-color: #159756;
|
||||||
|
}
|
||||||
|
25%, 49.9% {
|
||||||
|
background-color: #da4733;
|
||||||
|
}
|
||||||
|
50%, 74.9% {
|
||||||
|
background-color: #3b78e7;
|
||||||
|
}
|
||||||
|
75%, 100% {
|
||||||
|
background-color: #fdba2c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes preloader-front {
|
||||||
|
0% {
|
||||||
|
width: 0;
|
||||||
|
background-color: #da4733;
|
||||||
|
}
|
||||||
|
24.9% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #da4733;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
width: 0;
|
||||||
|
background-color: #3b78e7;
|
||||||
|
}
|
||||||
|
49.9% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #3b78e7;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
width: 0;
|
||||||
|
background-color: #fdba2c;
|
||||||
|
}
|
||||||
|
74.9% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #fdba2c;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
width: 0%;
|
||||||
|
background-color: #159756;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #159756;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes preloader-front {
|
||||||
|
0% {
|
||||||
|
width: 0;
|
||||||
|
background-color: #da4733;
|
||||||
|
}
|
||||||
|
24.9% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #da4733;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
width: 0;
|
||||||
|
background-color: #3b78e7;
|
||||||
|
}
|
||||||
|
49.9% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #3b78e7;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
width: 0;
|
||||||
|
background-color: #fdba2c;
|
||||||
|
}
|
||||||
|
74.9% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #fdba2c;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
width: 0%;
|
||||||
|
background-color: #159756;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
width: 50%;
|
||||||
|
background-color: #159756;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
font-family: Avenir Next, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
/*box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);*/
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
:root main > * + * {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2.2em;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: .875em;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
width: 400px;
|
||||||
|
max-width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap; /* 不换行 */
|
||||||
|
overflow: hidden; /* 隐藏溢出部分 */
|
||||||
|
text-overflow: ellipsis; /* 使用省略号表示被省略的部分 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-down {
|
||||||
|
vertical-align: top !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-20 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #000000 !important;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075), 0 0 8px rgb(0 0 0 / 60%) !important;
|
||||||
|
}
|
1
public/svg/download.svg
Normal file
1
public/svg/download.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1710580884953" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="30189" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M742.4 286.254545c-9.309091-9.309091-20.945455-13.963636-34.909091-13.963636-11.636364 0-25.6 4.654545-34.909091 13.963636l-111.709091 111.709091V69.818182c0-27.927273-20.945455-48.872727-48.872727-48.872727-27.927273 0-48.872727 20.945455-48.872727 48.872727v328.145454L349.090909 286.254545c-9.309091-9.309091-20.945455-13.963636-34.909091-13.963636s-25.6 4.654545-34.909091 13.963636c-9.309091 9.309091-13.963636 20.945455-13.963636 34.909091s4.654545 25.6 13.963636 34.909091l230.4 230.4 230.4-230.4c9.309091-9.309091 13.963636-20.945455 13.963637-34.909091 4.654545-13.963636-2.327273-25.6-11.636364-34.909091zM954.181818 707.490909c0-6.981818 0-11.636364-2.327273-16.290909L854.109091 395.636364c-6.981818-18.618182-25.6-32.581818-46.545455-32.581819h-11.636363c-4.654545 9.309091-9.309091 18.618182-18.618182 25.6l-72.145455 72.145455h65.163637l81.454545 246.690909H169.890909l81.454546-246.690909h65.163636l-72.145455-72.145455c-11.636364-11.636364-20.945455-27.927273-25.6-46.545454l-4.654545 20.945454c-20.945455 0-39.563636 13.963636-46.545455 32.581819L72.145455 693.527273c0 4.654545-2.327273 9.309091-2.327273 13.963636V954.181818c0 27.927273 20.945455 48.872727 48.872727 48.872727h786.618182c27.927273 0 48.872727-20.945455 48.872727-48.872727V707.490909z" fill="#fff" ></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
public/svg/webpage.svg
Normal file
1
public/svg/webpage.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M700.223 7.31l0.292 0.438 215.34 224.404 0.366 0.366 6.579 6.944V947.25c0 41.957-34.21 76.093-76.24 76.093H149.739a76.239 76.239 0 0 1-76.24-76.02V76.02C73.427 34.136 107.709 0 149.739 0h543.394l7.017 7.31z m-321.55 405.9s-67.247 63.887-72.145 112.276c0 0 72.658-101.238 196.994-162.857 0 0-49.632-6.871-124.848 50.582z m369.5 210.225c3.875-63.155-9.867-110.814-29.823-146.338 13.815-13.304 17.616-60.158 17.616-60.158 4.386-49.486-17.981-69.88-17.981-69.88-37.571-39.18-128.503-7.236-128.503-7.236-114.76 37.425-210.004 129.38-210.004 129.38-150.797 135.446-162.346 273.89-162.346 273.89-6.579 95.024 73.608 95.755 73.608 95.755 82.744 4.24 121.631-26.022 126.455-30.188 52.19 28.215 99.41 26.46 99.41 26.46 183.325-11.914 219.946-161.542 219.946-161.542h-121.85C587.288 740.9 505.787 727.45 505.787 727.45 425.6 710.93 424.87 623.435 424.87 623.435h323.303z m-39.837-257.736c39.91 30.846 11.988 92.539 5.994 104.527-40.203-65.787-97.656-83.183-97.656-83.183-121.705 73.242-150.577 108.62-150.577 108.62 28.945-25.218 68.417-23.39 68.417-23.39 90.347 10.233 87.423 88.007 87.423 88.372H424.87c7.31-31.65 18.348-45.173 22.88-49.632-83.33 72.584-134.278 180.547-134.278 180.547 15.35 47 57.161 88.592 91.955 110.375a135.958 135.958 0 0 1-56.284 20.467c-73.607 8.99-71.488-50.144-71.488-50.144 2.193-61.4 62.132-157.01 62.132-157.01 54.822-80.113 130.403-147.434 130.403-147.434 74.704-63.594 139.028-94.367 139.028-94.367 67.614-34.794 99.045-7.821 99.045-7.821z" fill="currColor"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
public/svg/website.svg
Normal file
1
public/svg/website.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1722327668331" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2692" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M544.938667 115.04c0 0-91.530667-12.437333-230.197333 90.912 0-0.010667-124.106667 114.826667-133.034667 201.802667C181.706667 407.765333 315.605333 225.738667 544.938667 115.04zM941.024 320.810667c25.429333-23.808 32.565333-108.170667 32.565333-108.170667C981.578667 123.84 940.330667 87.04 940.330667 87.04 871.093333 16.64 703.413333 74.090667 703.413333 74.090667c-211.637333 67.210667-387.285333 232.64-387.285333 232.64C37.973333 550.24 16.693333 799.125333 16.693333 799.125333c-11.946667 170.890667 135.754667 172.16 135.754667 172.16 152.64 7.573333 224.266667-46.858667 233.216-54.272 96.234667 50.656 183.381333 47.594667 183.381333 47.594667C907.050667 943.157333 974.613333 674.122667 974.613333 674.122667l-224.64 0c-50.581333 121.066667-200.938667 96.864-200.938667 96.864-147.733333-29.642667-149.077333-187.04-149.077333-187.04l596.181333 0 0.010667-0.042667C1003.210667 470.474667 977.898667 384.810667 941.024 320.810667zM753.568 158.784C529.088 290.56 475.861333 354.154667 475.861333 354.154667c53.354667-45.312 126.176-41.941333 126.176-41.941333 166.592 18.4 161.162667 158.144 161.28 158.741333L399.936 470.954667c13.578667-56.885333 33.877333-81.162667 42.197333-89.130667-153.802667 130.442667-247.573333 324.490667-247.573333 324.490667 28.202667 84.629333 105.312 159.477333 169.450667 198.464-48.053333 31.648-103.776 36.96-103.776 36.96-135.754667 16.117333-131.765333-90.165333-131.765333-90.165333C132.384 741.226667 242.890667 569.173333 242.890667 569.173333c101.12-144.042667 240.544-265.077333 240.544-265.077333 137.749333-114.005333 256.522667-169.472 256.522667-169.472 124.736-62.56 182.666667-14.154667 182.666667-14.154667l0-0.010667 0.032 0c73.621333 55.402667 22.016 166.368 11.029333 187.989333C859.552 190.24 753.568 158.784 753.568 158.784z" fill="#272636" p-id="2693"></path></svg>
|
After Width: | Height: | Size: 2.1 KiB |
9
routes/index.js
Normal file
9
routes/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
var express = require("express");
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
/* GET home page. */
|
||||||
|
router.get("/", function (req, res, next) {
|
||||||
|
res.render("index", { title: "在线网站下载" });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
9
routes/users.js
Normal file
9
routes/users.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
/* GET users listing. */
|
||||||
|
router.get('/', function(req, res, next) {
|
||||||
|
res.send('respond with a resource');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
12
socket/socket.js
Normal file
12
socket/socket.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Download full website pages.
|
||||||
|
const wget = require("../wget");
|
||||||
|
|
||||||
|
module.exports = (io) => {
|
||||||
|
io.on("connection", function (socket) {
|
||||||
|
socket.on("request", function (data) {
|
||||||
|
console.log("Request connection received %s", data.token);
|
||||||
|
// now graphing the website
|
||||||
|
wget(io, data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
3
views/error.hbs
Normal file
3
views/error.hbs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<h1>{{message}}</h1>
|
||||||
|
<h2>{{error.status}}</h2>
|
||||||
|
<pre>{{error.stack}}</pre>
|
40
views/index.hbs
Normal file
40
views/index.hbs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<header>
|
||||||
|
<div
|
||||||
|
aria-busy="true"
|
||||||
|
id="progress"
|
||||||
|
hidden
|
||||||
|
aria-label="Loading, please wait."
|
||||||
|
role="progressbar"
|
||||||
|
></div>
|
||||||
|
</header>
|
||||||
|
<main role="main">
|
||||||
|
<img
|
||||||
|
src="/svg/webpage.svg"
|
||||||
|
width="100"
|
||||||
|
alt="website"
|
||||||
|
/>
|
||||||
|
<div style="margin-top: 40px;">
|
||||||
|
<h1>Website Downloader</h1>
|
||||||
|
<h5 style="margin-top: 20px;">下载当前网站的所有静态资源,仅供学习使用!</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12" style="float: none; margin: 0 auto;">
|
||||||
|
<div id="custom-search-input">
|
||||||
|
<form method="get" class="form" action="/search">
|
||||||
|
<div class="mt-20" style="display: ruby-text">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" id="website" placeholder="https://www.baidu.com">
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-default" id="download" >提 交</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5 hidden id="nFilesP">下载的文件总数:<span id="nFiles" style="color: red;font-weight: bold">0</span></h5>
|
||||||
|
<p class="log" id="log"></p>
|
||||||
|
<button style="display: none" class="btn btn-success"><span> 下 载 </span><img class="icon-down" width="16" src="/svg/download.svg" alt="download" /> </button>
|
||||||
|
</main>
|
71
views/layout.hbs
Normal file
71
views/layout.hbs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cn">
|
||||||
|
<head>
|
||||||
|
<title>{{title}}</title>
|
||||||
|
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||||
|
<link rel="stylesheet" href="/stylesheets/bootstrap.min.css">
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{{body}}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let numberOfFiles = 0;
|
||||||
|
const downloadWebsite = document.getElementsByClassName('btn-success')[0];
|
||||||
|
|
||||||
|
// connect to current socket.io server
|
||||||
|
const socket = io.connect(document.URL);
|
||||||
|
if(!localStorage['token'])
|
||||||
|
localStorage['token']=generateToken(20);
|
||||||
|
|
||||||
|
// wait for events for this token
|
||||||
|
socket.on(localStorage['token'],(event)=>{
|
||||||
|
console.log(event);
|
||||||
|
document.getElementById('progress').hidden=false;
|
||||||
|
const log = document.getElementById('log');
|
||||||
|
if (event.progress==="Converting") {
|
||||||
|
log.innerHTML=(`<code>100%! 正在压缩中...</code><br>`)
|
||||||
|
} else if( event.progress==="Completed") {
|
||||||
|
document.getElementById('progress').hidden=true;
|
||||||
|
log.innerHTML=(`<code>压缩成功 !</code><br>`)
|
||||||
|
downloadWebsite.style='display:'
|
||||||
|
downloadWebsite.onclick=function() {
|
||||||
|
window.location='/sites/'+event.file+".zip";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(event.progress.includes('200 OK'))
|
||||||
|
numberOfFiles++;
|
||||||
|
document.getElementById('nFilesP').hidden=false;
|
||||||
|
document.getElementById('nFiles').innerHTML=numberOfFiles
|
||||||
|
|
||||||
|
log.innerHTML=(`<code> ${event.progress}</code><br>`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Download a website on click
|
||||||
|
const downloadBtn = document.getElementById("download");
|
||||||
|
downloadBtn.onclick=()=>{
|
||||||
|
const website = document.getElementById('website').value;
|
||||||
|
if (website) {
|
||||||
|
console.log("Now downloading the website ... %s",website)
|
||||||
|
socket.emit('request', { token: localStorage['token'] , website});
|
||||||
|
} else {
|
||||||
|
console.log("no url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate token for each user for the first time.
|
||||||
|
function generateToken(n) {
|
||||||
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
let token = '';
|
||||||
|
for(let i = 0; i < n; i++) {
|
||||||
|
token += chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
35
wget/index.js
Normal file
35
wget/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
var util = require('util'),
|
||||||
|
exec = require('child_process').exec;
|
||||||
|
var archiver = require('../archiver')
|
||||||
|
|
||||||
|
|
||||||
|
module.exports=(io,data)=>{
|
||||||
|
|
||||||
|
// download all website assets
|
||||||
|
/**
|
||||||
|
* wget --mirror --convert-links --adjust-extension --page-requisites
|
||||||
|
* --no-parent http://example.org
|
||||||
|
* --mirror – Makes (among other things) the download recursive.
|
||||||
|
* --convert-links – convert all the links (also to stuff like CSS stylesheets) to relative, so it will be suitable for offline viewing.
|
||||||
|
* --adjust-extension – Adds suitable extensions to filenames (html or css) depending on their content-type.
|
||||||
|
* --page-requisites – Download things like CSS style-sheets and images required to properly display the page offline.
|
||||||
|
* --no-parent – When recurring do not ascend to the parent directory. It useful for restricting the download to only a portion of the site.
|
||||||
|
*/
|
||||||
|
let website ="";
|
||||||
|
const child = exec(`wget -mkEpnp --no-if-modified-since ${data.website}`);
|
||||||
|
|
||||||
|
// read stdout from the current child.
|
||||||
|
child.stderr.on("data",(response)=>{
|
||||||
|
if(response.startsWith("Resolving "))
|
||||||
|
{
|
||||||
|
website= response.substring(response.indexOf('Resolve ')+11,response.indexOf(' ('))
|
||||||
|
}
|
||||||
|
io.emit(data.token,{progress:response})
|
||||||
|
})
|
||||||
|
|
||||||
|
child.stderr.on('close',(response)=>{
|
||||||
|
|
||||||
|
io.emit(data.token,{progress:"Converting"})
|
||||||
|
archiver(website,io,data)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user