Skip to content

Commit 8ceb220

Browse files
committed
Add with-detail flag and Fix some async, security issue
1 parent 34d43df commit 8ceb220

File tree

4 files changed

+470
-28
lines changed

4 files changed

+470
-28
lines changed

app.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ const Crawler = require('./crawler');
44

55
const program = new Command();
66

7-
program.version('1.0.4');
7+
program.version('1.0.5');
88
program.option('-u, --username <username>', 'velog 유저이름');
9-
program.option('-d, --delay <ms>', '요청 딜레이 시간')
10-
program.option('-c, --cert <access_token>', 'velog 유저 access_token')
9+
program.option('-d, --delay <ms>', '요청 딜레이 시간');
10+
program.option('-c, --cert <access_token>', 'velog 유저 access_token');
11+
program.option('-w, --with-detail', '시리즈, 썸네일 등 디테일한 정보 포함')
1112

1213
program.parse(process.argv);
1314

@@ -18,6 +19,7 @@ program.parse(process.argv);
1819
const crawler = new Crawler(program.username, {
1920
delay: program.delay || 0,
2021
cert: program.cert,
22+
withDetail: program.withDetail || false,
2123
});
2224

2325
console.log('📙 백업을 시작합니다 / velog-backup');

crawler/index.js

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { join } = require('path');
55
const { PostsQuery, PostQuery } = require('./query');
66

77
class Crawler {
8-
constructor(username, { delay, cert }) {
8+
constructor(username, { delay, cert, withDetail }) {
99
this.username = username;
1010

1111
if (!username) {
@@ -16,6 +16,7 @@ class Crawler {
1616
// options
1717
this.delay = delay;
1818
this.cert = cert;
19+
this.withDetail = withDetail;
1920

2021
this.__grahpqlURL = 'https://v2.velog.io/graphql';
2122
this.__api = axios.create({
@@ -104,22 +105,37 @@ class Crawler {
104105
}
105106

106107
const path = join('backup', 'content', `${title}.md`);
107-
108-
post.body = '---\n'
109-
+ `title: "${post.title}"\n`
110-
+ `description: "${post.short_description.replace(/\n/g, ' ')}"\n`
111-
+ `date: ${post.released_at}\n`
112-
+ `tags: ${JSON.stringify(post.tags)}\n`
113-
+ '---\n' + post.body;
108+
let frontmatter = '---\n'
109+
+ `title: "${post.title}"\n`
110+
+ `description: "${post.short_description.replace(/\n/g, ' ')}"\n`
111+
+ `date: ${post.released_at}\n`
112+
+ `tags: ${JSON.stringify(post.tags)}\n`;
114113

114+
if (this.withDetail) {
115+
if (post.thumbnail) {
116+
this.getThumbnailImage(post.thumbnail);
117+
frontmatter += `thumbnail: ${post.thumbnail}\n`;
118+
}
119+
120+
if (post.series) {
121+
frontmatter = frontmatter
122+
+ `series:\n`
123+
+ ` id: ${post.series.id}\n`
124+
+ ` name: ${post.series.name}\n`;
125+
}
126+
}
127+
128+
frontmatter += '---\n';
129+
post.body = frontmatter + post.body;
130+
115131
try {
116132
await fs.promises.writeFile(path, post.body, 'utf8');
117133
} catch (e) {
118134
console.error(`⚠️ 파일을 쓰는데 문제가 발생했습니다. / error = ${e} title = ${post.title}`);
119135
}
120136
}
121137

122-
async getImage(body) {
138+
getImage(body) {
123139
const regex = /!\[([^\]]*)\]\((.*?.png|.*?.jpeg|.*?.jpg|.*?.webp|.*?.svg|.*?.gif|.*?.tiff)\s*("(?:.*[^"])")?\s*\)|!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g;
124140

125141
body = body.replace(regex, (_, alt, url) => {
@@ -142,6 +158,20 @@ class Crawler {
142158
return body;
143159
}
144160

161+
getThumbnailImage(url) {
162+
const filename = url.replace(/\/\s*$/,'').split('/').slice(-2).join('-').trim();
163+
const path = join('backup', 'images', decodeURI(filename));
164+
165+
this.__api({
166+
method: 'get',
167+
url: encodeURI(decodeURI(url)),
168+
responseType: 'stream',
169+
})
170+
.then(resp => resp.data.pipe(fs.createWriteStream(path)))
171+
.catch(e => console.error(`⚠️ 이미지를 다운 받는데 오류가 발생했습니다 / url = ${url} , e = ${e}`));
172+
173+
return `/images/${filename}`;
174+
}
145175
};
146176

147177
module.exports = Crawler;

0 commit comments

Comments
 (0)