w-dwload-reel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/.eslintignore +3 -0
- package/.eslintrc.js +55 -0
- package/.jsdoc +25 -0
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/SECURITY.md +5 -0
- package/babel.config.js +16 -0
- package/dist/w-dwload-reel.umd.js +7 -0
- package/dist/w-dwload-reel.umd.js.map +1 -0
- package/docs/WDwloadReel.mjs.html +317 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/global.html +520 -0
- package/docs/index.html +84 -0
- package/docs/scripts/collapse.js +39 -0
- package/docs/scripts/commonNav.js +28 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +99 -0
- package/docs/styles/jsdoc.css +776 -0
- package/docs/styles/prettify.css +80 -0
- package/g.mjs +42 -0
- package/package.json +29 -0
- package/script.txt +17 -0
- package/src/WDwloadReel.mjs +245 -0
- package/test/WDwloadReel.test.mjs +47 -0
- package/toolg/addVersion.mjs +4 -0
- package/toolg/cleanFolder.mjs +4 -0
- package/toolg/gDistRollup.mjs +26 -0
- package/toolg/modifyReadme.mjs +4 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.pln {
|
|
2
|
+
color: #ddd;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/* string content */
|
|
6
|
+
.str {
|
|
7
|
+
color: #61ce3c;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* a keyword */
|
|
11
|
+
.kwd {
|
|
12
|
+
color: #fbde2d;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* a comment */
|
|
16
|
+
.com {
|
|
17
|
+
color: #aeaeae;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* a type name */
|
|
21
|
+
.typ {
|
|
22
|
+
color: #8da6ce;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* a literal value */
|
|
26
|
+
.lit {
|
|
27
|
+
color: #fbde2d;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* punctuation */
|
|
31
|
+
.pun {
|
|
32
|
+
color: #ddd;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* lisp open bracket */
|
|
36
|
+
.opn {
|
|
37
|
+
color: #000000;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* lisp close bracket */
|
|
41
|
+
.clo {
|
|
42
|
+
color: #000000;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* a markup tag name */
|
|
46
|
+
.tag {
|
|
47
|
+
color: #8da6ce;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* a markup attribute name */
|
|
51
|
+
.atn {
|
|
52
|
+
color: #fbde2d;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* a markup attribute value */
|
|
56
|
+
.atv {
|
|
57
|
+
color: #ddd;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* a declaration */
|
|
61
|
+
.dec {
|
|
62
|
+
color: #EF5050;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* a variable name */
|
|
66
|
+
.var {
|
|
67
|
+
color: #c82829;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* a function name */
|
|
71
|
+
.fun {
|
|
72
|
+
color: #4271ae;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Specify class=linenums on a pre to get line numbering */
|
|
76
|
+
ol.linenums {
|
|
77
|
+
margin-top: 0;
|
|
78
|
+
margin-bottom: 0;
|
|
79
|
+
padding-bottom: 2px;
|
|
80
|
+
}
|
package/g.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import WDwloadReel from './src/WDwloadReel.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async function test() {
|
|
6
|
+
|
|
7
|
+
//url
|
|
8
|
+
let url = `https://www.facebook.com/reel/3403300299820247`
|
|
9
|
+
|
|
10
|
+
//fp
|
|
11
|
+
let fp = './abc.mp4'
|
|
12
|
+
|
|
13
|
+
//funProg
|
|
14
|
+
let funProg = (prog, nn, na) => {
|
|
15
|
+
console.log('prog', `${prog.toFixed(2)}%`, nn, na)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//WDwloadReel
|
|
19
|
+
await WDwloadReel(url, fp, {
|
|
20
|
+
clean: true, //單一程序執行時, 事先清除之前暫存檔, 減少浪費硬碟空間
|
|
21
|
+
funProg,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
//len
|
|
25
|
+
let len = fs.statSync(fp).size
|
|
26
|
+
console.log('len', len)
|
|
27
|
+
|
|
28
|
+
console.log('done:', fp)
|
|
29
|
+
}
|
|
30
|
+
test()
|
|
31
|
+
.catch((err) => {
|
|
32
|
+
console.log('catch', err)
|
|
33
|
+
})
|
|
34
|
+
// prog 0.49% 1 99
|
|
35
|
+
// prog 5.05% 4 99
|
|
36
|
+
// ...
|
|
37
|
+
// prog 99.00% 98 99
|
|
38
|
+
// prog 100.00% 99 99
|
|
39
|
+
// len 8468825
|
|
40
|
+
// done: ./abc.mp4
|
|
41
|
+
|
|
42
|
+
//node g.mjs
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "w-dwload-reel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/w-dwload-reel.umd.js",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"lodash-es": "^4.18.1",
|
|
7
|
+
"wsemi": "^1.8.54"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"w-package-tools": "^1.1.7"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "mocha --parallel --timeout 60000",
|
|
14
|
+
"deploy": "gh-pages -d docs"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/yuda-lyu/w-dwload-reel.git"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"package",
|
|
22
|
+
"tool",
|
|
23
|
+
"facebook",
|
|
24
|
+
"reel",
|
|
25
|
+
"nodejs"
|
|
26
|
+
],
|
|
27
|
+
"author": "yuda-lyu(semisphere)",
|
|
28
|
+
"license": "MIT"
|
|
29
|
+
}
|
package/script.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#node toolg/addVersion.mjs
|
|
2
|
+
node toolg/modifyReadme.mjs
|
|
3
|
+
|
|
4
|
+
node toolg/cleanFolder.mjs
|
|
5
|
+
./node_modules/.bin/jsdoc -c .jsdoc
|
|
6
|
+
|
|
7
|
+
node toolg/gDistRollup.mjs
|
|
8
|
+
|
|
9
|
+
git add . -A
|
|
10
|
+
git commit -m 'modify: '
|
|
11
|
+
git push origin master:master
|
|
12
|
+
|
|
13
|
+
npm run deploy
|
|
14
|
+
|
|
15
|
+
#npm test
|
|
16
|
+
|
|
17
|
+
#npm publish
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import get from 'lodash-es/get.js'
|
|
3
|
+
import genPm from 'wsemi/src/genPm.mjs'
|
|
4
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
5
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
6
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
7
|
+
import getPathParent from 'wsemi/src/getPathParent.mjs'
|
|
8
|
+
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
9
|
+
import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 下載Facebook Reel為mp4檔案至本機指定位置
|
|
14
|
+
*
|
|
15
|
+
* @param {String} url 輸入Facebook Reel網址字串(facebook.com / fb.watch)
|
|
16
|
+
* @param {String} fp 輸入儲存video(*.mp4)檔案路徑字串
|
|
17
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
18
|
+
* @param {Function} [opt.funProg=null] 輸入回傳進度函數,傳入參數為prog代表進度百分比(0~100)、nn代表當前已下載bytes、na代表全部須下載bytes,預設null
|
|
19
|
+
* @returns {Promise} 回傳Promise,resolve回傳'ok',reject回傳錯誤訊息
|
|
20
|
+
* @example
|
|
21
|
+
* import fs from 'fs'
|
|
22
|
+
* import WDwloadReel from './src/WDwloadReel.mjs'
|
|
23
|
+
*
|
|
24
|
+
* async function test() {
|
|
25
|
+
*
|
|
26
|
+
* //url
|
|
27
|
+
* let url = 'https://www.facebook.com/reel/XXXXXXXXXX'
|
|
28
|
+
*
|
|
29
|
+
* //fp
|
|
30
|
+
* let fp = './abc.mp4'
|
|
31
|
+
*
|
|
32
|
+
* //funProg
|
|
33
|
+
* let funProg = (prog, nn, na) => {
|
|
34
|
+
* console.log('prog', `${prog.toFixed(2)}%`, nn, na)
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* //WDwloadReel
|
|
38
|
+
* await WDwloadReel(url, fp, {
|
|
39
|
+
* funProg,
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* //len
|
|
43
|
+
* let len = fs.statSync(fp).size
|
|
44
|
+
* console.log('len', len)
|
|
45
|
+
*
|
|
46
|
+
* console.log('done:', fp)
|
|
47
|
+
* }
|
|
48
|
+
* test()
|
|
49
|
+
* .catch((err) => {
|
|
50
|
+
* console.log('catch', err)
|
|
51
|
+
* })
|
|
52
|
+
* // prog 1.00% 23456 2345678
|
|
53
|
+
* // prog 2.00% 46912 2345678
|
|
54
|
+
* // ...
|
|
55
|
+
* // prog 99.00% 2322221 2345678
|
|
56
|
+
* // prog 100.00% 2345678 2345678
|
|
57
|
+
* // len 2345678
|
|
58
|
+
* // done: ./abc.mp4
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
async function WDwloadReel(url, fp, opt = {}) {
|
|
62
|
+
|
|
63
|
+
//funProg
|
|
64
|
+
let funProg = get(opt, 'funProg')
|
|
65
|
+
let bFunProg = isfun(funProg)
|
|
66
|
+
|
|
67
|
+
//check
|
|
68
|
+
if (!isestr(url)) {
|
|
69
|
+
return Promise.reject('invalid url')
|
|
70
|
+
}
|
|
71
|
+
if (!isestr(fp)) {
|
|
72
|
+
return Promise.reject('fp is not a string')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let isMp4 = (buf) => {
|
|
76
|
+
//mp4 magic: bytes 4..7 = 'ftyp' (ISO base media file type box), 再驗 bytes 8..11 為常見 mp4 brand, 排除 JPEG2000 等其他 ftyp 容器格式
|
|
77
|
+
if (buf.length < 12) {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
if (buf.slice(4, 8).toString('ascii') !== 'ftyp') {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
let brand = buf.slice(8, 12).toString('ascii')
|
|
84
|
+
return ['isom', 'mp41', 'mp42', 'avc1', 'iso2', 'iso5', 'iso6', 'mp4v'].includes(brand)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let getRealData = async (url) => {
|
|
88
|
+
|
|
89
|
+
//pm
|
|
90
|
+
let pm = genPm()
|
|
91
|
+
|
|
92
|
+
//check
|
|
93
|
+
if (!isestr(url)) {
|
|
94
|
+
pm.reject('invalid url')
|
|
95
|
+
return pm
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//parseString
|
|
99
|
+
let parseString = (string) => {
|
|
100
|
+
return JSON.parse(`{"text": "${string}"}`).text
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//fetch, 參數與解析機制來源: https://github.com/victorsouzaleal/fb-downloader-scrapper/blob/main/src/facebook.ts
|
|
104
|
+
let headers = {
|
|
105
|
+
'sec-fetch-user': '?1',
|
|
106
|
+
'sec-ch-ua-mobile': '?0',
|
|
107
|
+
'sec-fetch-site': 'none',
|
|
108
|
+
'sec-fetch-dest': 'document',
|
|
109
|
+
'sec-fetch-mode': 'navigate',
|
|
110
|
+
'cache-control': 'max-age=0',
|
|
111
|
+
'authority': 'www.facebook.com',
|
|
112
|
+
'upgrade-insecure-requests': '1',
|
|
113
|
+
'accept-language': 'en-GB,en;q=0.9,tr-TR;q=0.8,tr;q=0.7,en-US;q=0.6',
|
|
114
|
+
'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"',
|
|
115
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36',
|
|
116
|
+
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
|
117
|
+
'cookie': 'sb=Rn8BYQvCEb2fpMQZjsd6L382; datr=Rn8BYbyhXgw9RlOvmsosmVNT; c_user=100003164630629; _fbp=fb.1.1629876126997.444699739; wd=1920x939; spin=r.1004812505_b.trunk_t.1638730393_s.1_v.2_; xs=28%3A8ROnP0aeVF8XcQ%3A2%3A1627488145%3A-1%3A4916%3A%3AAcWIuSjPy2mlTPuZAeA2wWzHzEDuumXI89jH8a_QIV8; fr=0jQw7hcrFdas2ZeyT.AWVpRNl_4noCEs_hb8kaZahs-jA.BhrQqa.3E.AAA.0.0.BhrQqa.AWUu879ZtCw',
|
|
118
|
+
}
|
|
119
|
+
fetch(url, { headers })
|
|
120
|
+
.then((res) => {
|
|
121
|
+
|
|
122
|
+
//check
|
|
123
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
124
|
+
|
|
125
|
+
return res.text()
|
|
126
|
+
})
|
|
127
|
+
.then((data) => {
|
|
128
|
+
|
|
129
|
+
data = data.replace(/"/g, '"').replace(/&/g, '&')
|
|
130
|
+
let sdMatch = data.match(/"browser_native_sd_url":"(.*?)"/) || data.match(/"playable_url":"(.*?)"/) || data.match(/sd_src\s*:\s*"([^"]*)"/) || data.match(/(?<="src":")[^"]*(https:\/\/[^"]*)/)
|
|
131
|
+
let hdMatch = data.match(/"browser_native_hd_url":"(.*?)"/) || data.match(/"playable_url_quality_hd":"(.*?)"/) || data.match(/hd_src\s*:\s*"([^"]*)"/)
|
|
132
|
+
let titleMatch = data.match(/<meta\sname="description"\scontent="(.*?)"/)
|
|
133
|
+
let thumbMatch = data.match(/"preferred_thumbnail":{"image":{"uri":"(.*?)"/)
|
|
134
|
+
let duration = data.match(/"playable_duration_in_ms":[0-9]+/gm)
|
|
135
|
+
|
|
136
|
+
//check
|
|
137
|
+
if (sdMatch && sdMatch[1]) {
|
|
138
|
+
let result = {
|
|
139
|
+
url,
|
|
140
|
+
duration_ms: cint(duration[0].split(':')[1]),
|
|
141
|
+
sd: parseString(sdMatch[1]),
|
|
142
|
+
hd: hdMatch && hdMatch[1] ? parseString(hdMatch[1]) : '',
|
|
143
|
+
title: titleMatch && titleMatch[1] ? parseString(titleMatch[1]) : data.match(/<title>(.*?)<\/title>/)?.[1] ?? '',
|
|
144
|
+
thumbnail: thumbMatch && thumbMatch[1] ? parseString(thumbMatch[1]) : '',
|
|
145
|
+
}
|
|
146
|
+
pm.resolve(result)
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
pm.reject('no video url found in page')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
})
|
|
153
|
+
.catch((err) => {
|
|
154
|
+
pm.reject(err)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return pm
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//getRealData, 解析FB頁面取得mp4直連
|
|
161
|
+
let info
|
|
162
|
+
try {
|
|
163
|
+
info = await getRealData(url)
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return Promise.reject(err)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//check, 須有hd或sd其中之一
|
|
170
|
+
if (!info.hd && !info.sd) {
|
|
171
|
+
return Promise.reject('can not get hd/sd mp4 url (reel may be private/deleted/FB changed)')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//target, 優先hd
|
|
175
|
+
let target = info.hd || info.sd
|
|
176
|
+
|
|
177
|
+
//fetch
|
|
178
|
+
let res
|
|
179
|
+
try {
|
|
180
|
+
res = await fetch(target)
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
return Promise.reject(`fetch mp4 fail: ${err.message}`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//check
|
|
187
|
+
if (!res.ok) {
|
|
188
|
+
return Promise.reject(`HTTP ${res.status} ${res.statusText}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//total, Content-Length未必提供, 取不到視為0(中段不觸發funProg, 最後仍補100%)
|
|
192
|
+
let total = cint(res.headers.get('content-length'))
|
|
193
|
+
|
|
194
|
+
//讀取串流並計算進度
|
|
195
|
+
let reader = res.body.getReader()
|
|
196
|
+
let chunks = []
|
|
197
|
+
let received = 0
|
|
198
|
+
let progPre = -1
|
|
199
|
+
while (true) {
|
|
200
|
+
let { done, value } = await reader.read()
|
|
201
|
+
if (done) {
|
|
202
|
+
break
|
|
203
|
+
}
|
|
204
|
+
chunks.push(value)
|
|
205
|
+
received += value.length
|
|
206
|
+
|
|
207
|
+
//funProg, 整數百分比變動才觸發避免高頻
|
|
208
|
+
if (bFunProg && total > 0) {
|
|
209
|
+
let prog = (received / total) * 100
|
|
210
|
+
let progInt = Math.floor(prog)
|
|
211
|
+
if (progInt > progPre) {
|
|
212
|
+
progPre = progInt
|
|
213
|
+
funProg(prog, received, total)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//buf
|
|
219
|
+
let buf = Buffer.concat(chunks)
|
|
220
|
+
|
|
221
|
+
//check mp4 magic, 避免HTML錯誤頁面或非mp4回應被當mp4寫入
|
|
222
|
+
if (!isMp4(buf)) {
|
|
223
|
+
return Promise.reject(`downloaded but not valid mp4 (first 16 bytes: ${buf.slice(0, 16).toString('hex')})`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
//mkdir, 上層目錄不存在時自動建立
|
|
227
|
+
let dir = getPathParent(fp)
|
|
228
|
+
if (isestr(dir) && !fsIsFolder(dir)) {
|
|
229
|
+
fsCreateFolder(dir)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
//write
|
|
233
|
+
fs.writeFileSync(fp, buf)
|
|
234
|
+
|
|
235
|
+
//funProg, 最終補100%(涵蓋無Content-Length時中段未觸發的情況)
|
|
236
|
+
if (bFunProg) {
|
|
237
|
+
let na = total || buf.length
|
|
238
|
+
funProg(100, na, na)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return 'ok'
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
export default WDwloadReel
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import WDwloadReel from '../src/WDwloadReel.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
describe('WDwloadReel', function() {
|
|
7
|
+
|
|
8
|
+
async function test() {
|
|
9
|
+
|
|
10
|
+
//url
|
|
11
|
+
let url = `https://www.facebook.com/reel/3403300299820247`
|
|
12
|
+
|
|
13
|
+
//fp
|
|
14
|
+
let fp = './abc.mp4'
|
|
15
|
+
|
|
16
|
+
//funProg
|
|
17
|
+
let funProg = (prog, nn, na) => {
|
|
18
|
+
console.log('prog', `${prog.toFixed(2)}%`, nn, na)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//WDwloadReel
|
|
22
|
+
await WDwloadReel(url, fp, {
|
|
23
|
+
clean: true, //單一程序執行時, 事先清除之前暫存檔, 減少浪費硬碟空間
|
|
24
|
+
funProg,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
//len
|
|
28
|
+
let len = fs.statSync(fp).size
|
|
29
|
+
// console.log('len', len)
|
|
30
|
+
|
|
31
|
+
//unlinkSync
|
|
32
|
+
fs.unlinkSync(fp)
|
|
33
|
+
|
|
34
|
+
return len
|
|
35
|
+
}
|
|
36
|
+
// test()
|
|
37
|
+
// .catch((err) => {
|
|
38
|
+
// console.log('catch', err)
|
|
39
|
+
// })
|
|
40
|
+
|
|
41
|
+
it('download', async function() {
|
|
42
|
+
let r = 8468825
|
|
43
|
+
let rr = await test()
|
|
44
|
+
assert.strict.deepEqual(r, rr)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import rollupFiles from 'w-package-tools/src/rollupFiles.mjs'
|
|
2
|
+
import getFiles from 'w-package-tools/src/getFiles.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let fdSrc = './src'
|
|
6
|
+
let fdTar = './dist'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
rollupFiles({
|
|
10
|
+
fns: 'WDwloadReel.mjs',
|
|
11
|
+
fdSrc,
|
|
12
|
+
fdTar,
|
|
13
|
+
// nameDistType: 'kebabCase',
|
|
14
|
+
hookNameDist: () => {
|
|
15
|
+
return 'w-dwload-reel'
|
|
16
|
+
},
|
|
17
|
+
globals: {
|
|
18
|
+
'path': 'path',
|
|
19
|
+
'fs': 'fs',
|
|
20
|
+
},
|
|
21
|
+
external: [
|
|
22
|
+
'path',
|
|
23
|
+
'fs',
|
|
24
|
+
],
|
|
25
|
+
})
|
|
26
|
+
|