w-dwdata-hub 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 +168 -0
- package/SECURITY.md +5 -0
- package/babel.config.js +16 -0
- package/dist/w-dwdata-hub.umd.js +7 -0
- package/dist/w-dwdata-hub.umd.js.map +1 -0
- package/docs/WDwdataHub.mjs.html +136 -0
- package/docs/decodeEntities.mjs.html +129 -0
- package/docs/extractDivContent.mjs.html +147 -0
- package/docs/fetchAiNewsAggregator.mjs.html +155 -0
- package/docs/fetchAisixiang.mjs.html +1038 -0
- package/docs/fetchCnyes.mjs.html +260 -0
- package/docs/fetchGuancha.mjs.html +1007 -0
- package/docs/fetchHackerNews.mjs.html +189 -0
- package/docs/fetchMoneydj.mjs.html +251 -0
- package/docs/fetchMops.mjs.html +385 -0
- package/docs/fetchRSS.mjs.html +160 -0
- package/docs/fetchStatementdog.mjs.html +213 -0
- package/docs/fetchTaifex.mjs.html +513 -0
- package/docs/fetchTpex3insti.mjs.html +278 -0
- package/docs/fetchTpexMargin.mjs.html +248 -0
- package/docs/fetchTpexStock.mjs.html +199 -0
- package/docs/fetchTwDataHoliday.mjs.html +236 -0
- package/docs/fetchTwseMargin.mjs.html +253 -0
- package/docs/fetchTwseStock.mjs.html +188 -0
- package/docs/fetchTwseT86.mjs.html +208 -0
- package/docs/fetchWithRetry.mjs.html +376 -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/getOptFetch.mjs.html +185 -0
- package/docs/global.html +22649 -0
- package/docs/htmlToMarkdown.mjs.html +168 -0
- package/docs/index.html +84 -0
- package/docs/isYmd.mjs.html +114 -0
- package/docs/parseIntComma.mjs.html +100 -0
- package/docs/safeFilename.mjs.html +102 -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/docs/toDatetimeUTC8.mjs.html +143 -0
- package/docs/toRocDate.mjs.html +107 -0
- package/g.mjs +73 -0
- package/package.json +31 -0
- package/script.txt +17 -0
- package/src/WDwdataHub.mjs +64 -0
- package/src/decodeEntities.mjs +57 -0
- package/src/extractDivContent.mjs +75 -0
- package/src/fetchAiNewsAggregator.mjs +83 -0
- package/src/fetchAisixiang.mjs +966 -0
- package/src/fetchCnyes.mjs +188 -0
- package/src/fetchGuancha.mjs +935 -0
- package/src/fetchHackerNews.mjs +117 -0
- package/src/fetchMoneydj.mjs +179 -0
- package/src/fetchMops.mjs +313 -0
- package/src/fetchRSS.mjs +88 -0
- package/src/fetchStatementdog.mjs +141 -0
- package/src/fetchTaifex.mjs +441 -0
- package/src/fetchTpex3insti.mjs +206 -0
- package/src/fetchTpexMargin.mjs +176 -0
- package/src/fetchTpexStock.mjs +127 -0
- package/src/fetchTwDataHoliday.mjs +164 -0
- package/src/fetchTwseMargin.mjs +181 -0
- package/src/fetchTwseStock.mjs +116 -0
- package/src/fetchTwseT86.mjs +136 -0
- package/src/fetchWithRetry.mjs +304 -0
- package/src/getOptFetch.mjs +113 -0
- package/src/htmlToMarkdown.mjs +96 -0
- package/src/isYmd.mjs +42 -0
- package/src/parseIntComma.mjs +28 -0
- package/src/safeFilename.mjs +30 -0
- package/src/toDatetimeUTC8.mjs +71 -0
- package/src/toRocDate.mjs +35 -0
- package/test/WDwdataHub.test.mjs +63 -0
- package/test/decodeEntities.test.mjs +42 -0
- package/test/extractDivContent.test.mjs +48 -0
- package/test/fetchAiNewsAggregator.test.mjs +79 -0
- package/test/fetchAisixiang.test.mjs +225 -0
- package/test/fetchCnyes.test.mjs +61 -0
- package/test/fetchGuancha.test.mjs +190 -0
- package/test/fetchHackerNews.test.mjs +78 -0
- package/test/fetchMoneydj.test.mjs +82 -0
- package/test/fetchMops.test.mjs +106 -0
- package/test/fetchRSS.test.mjs +85 -0
- package/test/fetchStatementdog.test.mjs +69 -0
- package/test/fetchTaifex.test.mjs +99 -0
- package/test/fetchTpex3insti.test.mjs +90 -0
- package/test/fetchTpexMargin.test.mjs +94 -0
- package/test/fetchTpexStock.test.mjs +68 -0
- package/test/fetchTwDataHoliday.test.mjs +100 -0
- package/test/fetchTwseMargin.test.mjs +81 -0
- package/test/fetchTwseStock.test.mjs +80 -0
- package/test/fetchTwseT86.test.mjs +76 -0
- package/test/fetchWithRetry.test.mjs +174 -0
- package/test/htmlToMarkdown.test.mjs +71 -0
- package/test/isYmd.test.mjs +56 -0
- package/test/parseIntComma.test.mjs +38 -0
- package/test/safeFilename.test.mjs +35 -0
- package/test/toDatetimeUTC8.test.mjs +65 -0
- package/test/toRocDate.test.mjs +38 -0
- package/test/tools/encodeBig5.mjs +80 -0
- package/test/tools/fixtures.mjs +539 -0
- package/test/tools/hasChrome.mjs +39 -0
- package/test/tools/serverForTest.mjs +397 -0
- package/toolg/addVersion.mjs +4 -0
- package/toolg/cleanFolder.mjs +5 -0
- package/toolg/gDistRollup.mjs +49 -0
- package/toolg/modifyReadme.mjs +4 -0
package/.editorconfig
ADDED
package/.eslintignore
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
'env': {
|
|
3
|
+
'browser': true,
|
|
4
|
+
'es6': true,
|
|
5
|
+
'mocha': true
|
|
6
|
+
},
|
|
7
|
+
'extends': ['plugin:vue/base', 'plugin:vue/essential', 'standard'],
|
|
8
|
+
'plugins': [
|
|
9
|
+
'vue'
|
|
10
|
+
],
|
|
11
|
+
'globals': {
|
|
12
|
+
'Atomics': 'readonly',
|
|
13
|
+
'SharedArrayBuffer': 'readonly'
|
|
14
|
+
},
|
|
15
|
+
'parser': 'vue-eslint-parser',
|
|
16
|
+
'parserOptions': {
|
|
17
|
+
'parser': '@babel/eslint-parser',
|
|
18
|
+
'ecmaVersion': 2019,
|
|
19
|
+
'sourceType': 'module',
|
|
20
|
+
},
|
|
21
|
+
'rules': {
|
|
22
|
+
'generator-star-spacing': 'off',
|
|
23
|
+
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
|
24
|
+
'indent': ['error', 4],
|
|
25
|
+
'no-console': 'off',
|
|
26
|
+
'comma-dangle': ['error', {
|
|
27
|
+
'arrays': 'ignore',
|
|
28
|
+
'objects': 'ignore',
|
|
29
|
+
'imports': 'never',
|
|
30
|
+
'exports': 'never',
|
|
31
|
+
'functions': 'ignore'
|
|
32
|
+
}],
|
|
33
|
+
'no-multiple-empty-lines': ['error', { 'max': 2, 'maxEOF': 2 }],
|
|
34
|
+
//'space-before-function-paren': ['error', { 'anonymous': 'always', 'named': 'never' }],
|
|
35
|
+
'space-before-function-paren': 'off',
|
|
36
|
+
'spaced-comment': 'off',
|
|
37
|
+
'brace-style': ['error', 'stroustrup'],
|
|
38
|
+
'padded-blocks': 'off',
|
|
39
|
+
'no-constant-condition': 'off',
|
|
40
|
+
'camelcase': 'off',
|
|
41
|
+
'no-new': 'off',
|
|
42
|
+
'prefer-const': 'off',
|
|
43
|
+
'quote-props': ['error', 'consistent'],
|
|
44
|
+
'dot-notation': 'off',
|
|
45
|
+
'standard/no-callback-literal': 'off',
|
|
46
|
+
'quotes': ['error', 'single', { 'allowTemplateLiterals': true }],
|
|
47
|
+
'prefer-promise-reject-errors': 'off',
|
|
48
|
+
'no-unused-vars': ['error', { 'args': 'none', 'ignoreRestSiblings': true, 'argsIgnorePattern': '^_' }],
|
|
49
|
+
'node/no-callback-literal': 'off',
|
|
50
|
+
'prefer-regex-literals': 'off',
|
|
51
|
+
'array-callback-return': 'off',
|
|
52
|
+
'no-unreachable-loop': 'off',
|
|
53
|
+
'vue/multi-word-component-names': 'off',
|
|
54
|
+
}
|
|
55
|
+
}
|
package/.jsdoc
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tags": {
|
|
3
|
+
"allowUnknownTags": false
|
|
4
|
+
},
|
|
5
|
+
"source": {
|
|
6
|
+
"include": ["src/", "server/"],
|
|
7
|
+
"includePattern": ".+\\.(js(doc|x)?|mjs|vue)$",
|
|
8
|
+
"excludePattern": "(node_modules/|docs)"
|
|
9
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
"plugins/markdown",
|
|
12
|
+
"node_modules/jsdoc-vuejs"
|
|
13
|
+
],
|
|
14
|
+
"opts": {
|
|
15
|
+
"template": "node_modules/docdash",
|
|
16
|
+
"encoding": "utf8",
|
|
17
|
+
"destination": "docs/",
|
|
18
|
+
"recurse": true,
|
|
19
|
+
"verbose": true
|
|
20
|
+
},
|
|
21
|
+
"templates": {
|
|
22
|
+
"cleverLinks": false,
|
|
23
|
+
"monospaceLinks": false
|
|
24
|
+
}
|
|
25
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 semisphere(yu-da, lyu 呂昱達)
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# w-dwdata-hub
|
|
2
|
+
A download data hub.
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+
[](https://npmjs.org/package/w-dwdata-hub)
|
|
6
|
+
[](https://npmjs.org/package/w-dwdata-hub)
|
|
7
|
+
[](https://npmjs.org/package/w-dwdata-hub)
|
|
8
|
+
[](https://npmjs.org/package/w-dwdata-hub)
|
|
9
|
+
[](https://www.jsdelivr.com/package/npm/w-dwdata-hub)
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
To view documentation or get support, visit [docs](https://yuda-lyu.github.io/w-dwdata-hub/global.html).
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Using npm(ES6 module):
|
|
17
|
+
```alias
|
|
18
|
+
npm i w-dwdata-hub
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Note:
|
|
22
|
+
- All fetch functions use node's built-in `fetch`, so node 18+ is required.
|
|
23
|
+
- `fetchMops` needs Chrome installed (playwright uses `channel: 'chrome'`), or set env `CHROME_PATH` to the browser executable.
|
|
24
|
+
- `fetchGuancha` delegates fetching to `w-fetch-web`, which needs `curl` in system PATH.
|
|
25
|
+
|
|
26
|
+
#### Functions:
|
|
27
|
+
|
|
28
|
+
General news, each returns an array of `{url, time, title, description, from}`:
|
|
29
|
+
| function | description |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `fetchRSS(rssUrl, opt)` | fetch an RSS or Atom feed |
|
|
32
|
+
| `fetchHackerNews(limit, opt)` | fetch the latest Hacker News stories |
|
|
33
|
+
| `fetchAiNewsAggregator(opt)` | fetch the latest 24h AI news digest |
|
|
34
|
+
|
|
35
|
+
Taiwan stock market news:
|
|
36
|
+
| function | description |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `fetchCnyes(opt)` | fetch Anue(cnyes) tw_stock news, returns `{time, title, link}` |
|
|
39
|
+
| `fetchMoneydj(opt)` | fetch MoneyDJ tw stock news(MB06), returns `{time, title, link}` |
|
|
40
|
+
| `fetchStatementdog(opt)` | fetch StatementDog latest news, returns `{time, title, link}` |
|
|
41
|
+
| `fetchMops(opt)` | fetch MOPS material announcements of the day, by playwright |
|
|
42
|
+
|
|
43
|
+
Taiwan stock market data:
|
|
44
|
+
| function | description |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `fetchTwseStock(dateStr, stockCode, opt)` | fetch TWSE listed stock daily quotes |
|
|
47
|
+
| `fetchTpexStock(dateStr, stockCodes, opt)` | fetch TPEX OTC stock daily quotes |
|
|
48
|
+
| `fetchTwseMargin(dateStr, stockCodes, opt)` | fetch TWSE margin trading balance |
|
|
49
|
+
| `fetchTpexMargin(dateStr, stockCodes, opt)` | fetch TPEX margin trading balance |
|
|
50
|
+
| `fetchTwseT86(dateStr, stockCodes, opt)` | fetch TWSE institutional investors net buy/sell |
|
|
51
|
+
| `fetchTpex3insti(dateStr, stockCodes, opt)` | fetch TPEX institutional investors net buy/sell |
|
|
52
|
+
| `fetchTaifex(dateStr, opt)` | fetch TAIFEX TX quotes, institutional open interest and Put/Call ratio |
|
|
53
|
+
| `fetchTwDataHoliday(checkDate, opt)` | fetch TWSE market holiday schedule |
|
|
54
|
+
|
|
55
|
+
Chinese commentary sites, each is an object holding several functions:
|
|
56
|
+
| function | description |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `fetchAisixiang.fetchAuthorsList(opt)` | fetch the columnist list of aisixiang.com |
|
|
59
|
+
| `fetchAisixiang.fetchTopicsList(opt)` | fetch the curated topic list of aisixiang.com |
|
|
60
|
+
| `fetchAisixiang.fetchAuthorArticles(opt)` | list articles by author `name` or `slug` |
|
|
61
|
+
| `fetchAisixiang.fetchKeywordArticles(keyword, opt)` | list articles by keyword tag |
|
|
62
|
+
| `fetchAisixiang.fetchTitleArticles(keyword, opt)` | list articles by title search |
|
|
63
|
+
| `fetchAisixiang.fetchTopicArticles(opt)` | list articles by topic `keyword` or `id` |
|
|
64
|
+
| `fetchAisixiang.fetchArticle(opt)` | fetch one article by `aid` or `url`, into markdown |
|
|
65
|
+
| `fetchGuancha.fetchAuthorsList(opt)` | fetch the A-Z author index of guancha.cn |
|
|
66
|
+
| `fetchGuancha.fetchAuthorArticles(opt)` | list articles by author `name` or `slug` |
|
|
67
|
+
| `fetchGuancha.fetchTopicArticles(opt)` | list articles by topic `name` or `slug` |
|
|
68
|
+
| `fetchGuancha.fetchArticle(opt)` | fetch one article by `url`, into markdown |
|
|
69
|
+
|
|
70
|
+
#### Example:
|
|
71
|
+
> **Link:** [[dev source code](https://github.com/yuda-lyu/w-dwdata-hub/blob/master/g.mjs)]
|
|
72
|
+
```alias
|
|
73
|
+
import wi from 'w-dwdata-hub'
|
|
74
|
+
|
|
75
|
+
let test = async () => {
|
|
76
|
+
|
|
77
|
+
//綜合新聞: RSS訂閱源, 回傳統一格式{url,time,title,description,from}
|
|
78
|
+
let r1 = await wi.fetchRSS('https://www.ptt.cc/atom/Gossiping.xml', { showLog: false })
|
|
79
|
+
console.log('rss:', r1.length, r1[0].time, r1[0].title)
|
|
80
|
+
// => rss: 19 2026-08-07 09:30:19 [問卦] 公視是怎麼走到今天這一步的?
|
|
81
|
+
|
|
82
|
+
//綜合新聞: Hacker News最新文章, 可指定取回篇數
|
|
83
|
+
let r2 = await wi.fetchHackerNews(5, { showLog: false })
|
|
84
|
+
console.log('hackerNews:', r2.length, r2[0].from, r2[0].title)
|
|
85
|
+
// => hackerNews: 5 Hacker News Romania blasts rock to divert water from drought-hit Danube to nuclear reactor
|
|
86
|
+
|
|
87
|
+
//綜合新聞: AI News Aggregator近24小時彙整
|
|
88
|
+
let r3 = await wi.fetchAiNewsAggregator({ showLog: false })
|
|
89
|
+
console.log('aiNews:', r3.length, r3[0].from)
|
|
90
|
+
// => aiNews: 914 夕小瑶科技说
|
|
91
|
+
|
|
92
|
+
//台股新聞: 鉅亨網, 逐頁抓至targetTotal則
|
|
93
|
+
let r4 = await wi.fetchCnyes({ targetTotal: 30, showLog: false })
|
|
94
|
+
console.log('cnyes:', r4.length, r4[0].time, r4[0].link)
|
|
95
|
+
// => cnyes: 30 2026-08-07 09:20:46 https://news.cnyes.com/news/id/6563766
|
|
96
|
+
|
|
97
|
+
//台股資料: 證交所上市個股日成交資訊, 指定日期會由整月資料過濾為當日單筆
|
|
98
|
+
let r5 = await wi.fetchTwseStock('20260806', '2330', { showLog: false })
|
|
99
|
+
console.log('twseStock:', r5.stat, r5.data.length, r5.data[0][6])
|
|
100
|
+
// => twseStock: OK 1 2,365.00
|
|
101
|
+
|
|
102
|
+
//台股資料: 櫃買中心上櫃融資融券, 未指定個股則回全市場
|
|
103
|
+
let r6 = await wi.fetchTpexMargin('20260806', ['6488'], { showLog: false })
|
|
104
|
+
console.log('tpexMargin:', r6.source, r6.count, r6.data[0].marginChange)
|
|
105
|
+
// => tpexMargin: tpex_margin 1 -694
|
|
106
|
+
|
|
107
|
+
//台股資料: 期交所台指期、三大法人未平倉與Put/Call Ratio, 單支失敗僅記於errors
|
|
108
|
+
let r7 = await wi.fetchTaifex('20260806', { showLog: false })
|
|
109
|
+
console.log('taifex:', r7.futures.tx.close, r7.pcRatio.ratio, r7.errors)
|
|
110
|
+
// => taifex: 44280 109.14 []
|
|
111
|
+
|
|
112
|
+
//台股資料: 證交所休市日期, 給定日期則另回傳該日是否休市
|
|
113
|
+
let r8 = await wi.fetchTwDataHoliday('20260101', { showLog: false })
|
|
114
|
+
console.log('holiday:', r8.dataYear, r8.totalHolidays, r8.isHoliday, r8.holidayName)
|
|
115
|
+
// => holiday: 2026 22 true 中華民國開國紀念日
|
|
116
|
+
|
|
117
|
+
//中文評論網站: 愛思想, 依作者中文名查專欄文章清單
|
|
118
|
+
let r9 = await wi.fetchAisixiang.fetchAuthorArticles({ name: '葛兆光', showLog: false })
|
|
119
|
+
console.log('aisixiang:', r9.status, r9.resolved.slug, r9.count)
|
|
120
|
+
// => aisixiang: success gezhaoguang 170
|
|
121
|
+
|
|
122
|
+
//中文評論網站: 愛思想, 抓單篇文章轉為含frontmatter之Markdown
|
|
123
|
+
let r10 = await wi.fetchAisixiang.fetchArticle({ aid: '146669', showLog: false })
|
|
124
|
+
console.log('aisixiang:', r10.status, r10.title, r10.chars)
|
|
125
|
+
// => aisixiang: success 葛兆光:禅宗与中国文化 16986
|
|
126
|
+
|
|
127
|
+
//中文評論網站: 觀察者網, 依主題中文名查文章清單, 自動選用可翻頁之欄目slug
|
|
128
|
+
let r11 = await wi.fetchGuancha.fetchTopicArticles({ name: '财经', maxPages: 1, showLog: false })
|
|
129
|
+
console.log('guancha:', r11.status, r11.resolved.slug, r11.count)
|
|
130
|
+
// => guancha: success CaiJing 30
|
|
131
|
+
|
|
132
|
+
//查無資料時回status為success且count為0, 抓取失敗才回status為error, 兩者可明確區分
|
|
133
|
+
let r12 = await wi.fetchGuancha.fetchAuthorArticles({ name: '不存在的作者XYZ', showLog: false })
|
|
134
|
+
console.log('guancha:', r12.status, r12.count, r12.message)
|
|
135
|
+
// => guancha: success 0 "不存在的作者XYZ" 不在觀察者網作者索引中(共 641 位)。尚無此作者文章。提醒:本函數不轉繁簡,呼叫端負責用站方登錄字形(簡體)。
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
await test()
|
|
139
|
+
.catch((err) => {
|
|
140
|
+
console.log(err)
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Common options:
|
|
145
|
+
各抓取函數皆接受`opt`設定物件,以下為共用項目:
|
|
146
|
+
|
|
147
|
+
| key | type | default | description |
|
|
148
|
+
| --- | --- | --- | --- |
|
|
149
|
+
| `timeout` | Integer | 依函數而異 | 單次請求逾時毫秒 |
|
|
150
|
+
| `maxRetries` | Integer | 依函數而異 | 最大重試次數,含初始共執行`maxRetries+1`次 |
|
|
151
|
+
| `baseDelayMs` | Integer | 依函數而異 | 重試之線性退避基礎毫秒 |
|
|
152
|
+
| `maxDelayMs` | Integer | 依函數而異 | 重試之線性退避上限毫秒 |
|
|
153
|
+
| `showLog` | Boolean | `true` | 是否顯示過程與重試訊息 |
|
|
154
|
+
| `baseUrl`/`url`/`apiBase` | String | 官方網址 | 來源網址,供測試或改指向鏡像時覆寫 |
|
|
155
|
+
|
|
156
|
+
重試條件為HTTP 5xx與429及網路層錯誤(含逾時);HTTP 4xx(429除外)預設不重試,僅部份函數另納入403或404(例如鉅亨網、財報狗、MoneyDJ納入403,RSS納入404)。
|
|
157
|
+
|
|
158
|
+
#### Error contract:
|
|
159
|
+
- 參數不合法(如日期非`YYYYMMDD`、必填參數缺漏)一律`throw`,不會靜默回傳空結果。
|
|
160
|
+
- 抓取或解析失敗一律`reject`;`fetchAisixiang`與`fetchGuancha`則改回傳`{status:'error',...}`結果物件。
|
|
161
|
+
- 「查詢成功但結果為0筆」與「抓取失敗」明確區分:前者回`{status:'success',count:0,message}`,後者回`{status:'error'}`或`reject`。
|
|
162
|
+
- API結構與預期不符時一律fail-loud拋錯(如櫃買中心融資融券之欄位佈局檢核、櫃買中心三大法人之24欄結構檢核),不以固定index靜默解析出錯置資料。
|
|
163
|
+
|
|
164
|
+
#### Tests:
|
|
165
|
+
```alias
|
|
166
|
+
npm test
|
|
167
|
+
```
|
|
168
|
+
測試不連線外部網站,改以`test/tools/serverForTest.mjs`啟動本機HTTP伺服器提供各官方API格式之固定回應;`fetchMops`之瀏覽器案例於無Chrome環境時自動跳過。
|
package/SECURITY.md
ADDED
package/babel.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
'presets': [
|
|
3
|
+
['@babel/preset-env', {
|
|
4
|
+
'useBuiltIns': 'entry',
|
|
5
|
+
'corejs': 3
|
|
6
|
+
}]
|
|
7
|
+
],
|
|
8
|
+
'plugins': [
|
|
9
|
+
'@babel/plugin-transform-runtime',
|
|
10
|
+
'@babel/plugin-proposal-export-default-from',
|
|
11
|
+
// '@babel/plugin-syntax-import-assertions', //已被棄用
|
|
12
|
+
// '@babel/plugin-proposal-nullish-coalescing-operator', //browser與nodejs已支援
|
|
13
|
+
// '@babel/plugin-proposal-object-rest-spread', //browser與nodejs已支援
|
|
14
|
+
// '@babel/plugin-proposal-optional-chaining', //browser與nodejs已支援
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* w-dwdata-hub v1.0.0
|
|
3
|
+
* (c) 2018-2021 yuda-lyu(semisphere)
|
|
4
|
+
* Released under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("w-fetch-web"),require("cheerio"),require("playwright"),require("rss-parser")):"function"==typeof define&&define.amd?define(["w-fetch-web","cheerio","playwright","rss-parser"],e):(t="undefined"!=typeof globalThis?globalThis:t||self)["w-dwdata-hub"]=e(t["w-fetch-web"],t.cheerio,t.playwright,t["rss-parser"])}(this,function(t,e,r,n){"use strict";function a(t){var e=Object.create(null);return t&&Object.keys(t).forEach(function(r){if("default"!==r){var n=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,n.get?n:{enumerable:!0,get:function(){return t[r]}})}}),e.default=t,Object.freeze(e)}var s=a(e),i=Array.isArray,o="object"==typeof global&&global&&global.Object===Object&&global,l="object"==typeof self&&self&&self.Object===Object&&self,u=o||l||Function("return this")(),c=u.Symbol,h=Object.prototype,f=h.hasOwnProperty,g=h.toString,d=c?c.toStringTag:void 0;var m=Object.prototype.toString;var p=c?c.toStringTag:void 0;function w(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":p&&p in Object(t)?function(t){var e=f.call(t,d),r=t[d];try{t[d]=void 0;var n=!0}catch(t){}var a=g.call(t);return n&&(e?t[d]=r:delete t[d]),a}(t):function(t){return m.call(t)}(t)}function y(t){return null!=t&&"object"==typeof t}function $(t){return"symbol"==typeof t||y(t)&&"[object Symbol]"==w(t)}var b=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,S=/^\w*$/;function _(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}var v,D=u["__core-js_shared__"],M=(v=/[^.]+$/.exec(D&&D.keys&&D.keys.IE_PROTO||""))?"Symbol(src)_1."+v:"";var A=Function.prototype.toString;var x=/^\[object .+?Constructor\]$/,T=Function.prototype,E=Object.prototype,O=T.toString,k=E.hasOwnProperty,j=RegExp("^"+O.call(k).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function L(t){if(!_(t)||(e=t,M&&M in e))return!1;var e,r=function(t){if(!_(t))return!1;var e=w(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}(t)?j:x;return r.test(function(t){if(null!=t){try{return A.call(t)}catch(t){}try{return t+""}catch(t){}}return""}(t))}function P(t,e){var r=function(t,e){return null==t?void 0:t[e]}(t,e);return L(r)?r:void 0}var C=P(Object,"create");var I=Object.prototype.hasOwnProperty;var U=Object.prototype.hasOwnProperty;function R(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}function Y(t,e){return t===e||t!=t&&e!=e}function N(t,e){for(var r=t.length;r--;)if(Y(t[r][0],e))return r;return-1}R.prototype.clear=function(){this.__data__=C?C(null):{},this.size=0},R.prototype.delete=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e},R.prototype.get=function(t){var e=this.__data__;if(C){var r=e[t];return"__lodash_hash_undefined__"===r?void 0:r}return I.call(e,t)?e[t]:void 0},R.prototype.has=function(t){var e=this.__data__;return C?void 0!==e[t]:U.call(e,t)},R.prototype.set=function(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=C&&void 0===e?"__lodash_hash_undefined__":e,this};var F=Array.prototype.splice;function H(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}H.prototype.clear=function(){this.__data__=[],this.size=0},H.prototype.delete=function(t){var e=this.__data__,r=N(e,t);return!(r<0)&&(r==e.length-1?e.pop():F.call(e,r,1),--this.size,!0)},H.prototype.get=function(t){var e=this.__data__,r=N(e,t);return r<0?void 0:e[r][1]},H.prototype.has=function(t){return N(this.__data__,t)>-1},H.prototype.set=function(t,e){var r=this.__data__,n=N(r,t);return n<0?(++this.size,r.push([t,e])):r[n][1]=e,this};var q=P(u,"Map");function z(t,e){var r,n,a=t.__data__;return("string"==(n=typeof(r=e))||"number"==n||"symbol"==n||"boolean"==n?"__proto__"!==r:null===r)?a["string"==typeof e?"string":"hash"]:a.map}function W(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e<r;){var n=t[e];this.set(n[0],n[1])}}W.prototype.clear=function(){this.size=0,this.__data__={hash:new R,map:new(q||H),string:new R}},W.prototype.delete=function(t){var e=z(this,t).delete(t);return this.size-=e?1:0,e},W.prototype.get=function(t){return z(this,t).get(t)},W.prototype.has=function(t){return z(this,t).has(t)},W.prototype.set=function(t,e){var r=z(this,t),n=r.size;return r.set(t,e),this.size+=r.size==n?0:1,this};function B(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new TypeError("Expected a function");var r=function(){var n=arguments,a=e?e.apply(this,n):n[0],s=r.cache;if(s.has(a))return s.get(a);var i=t.apply(this,n);return r.cache=s.set(a,i)||s,i};return r.cache=new(B.Cache||W),r}B.Cache=W;var K,G,X,Z=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,J=/\\(\\)?/g,V=(K=function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(""),t.replace(Z,function(t,r,n,a){e.push(n?a.replace(J,"$1"):r||t)}),e},G=B(K,function(t){return 500===X.size&&X.clear(),t}),X=G.cache,G),Q=V;var tt=c?c.prototype:void 0,et=tt?tt.toString:void 0;function rt(t){if("string"==typeof t)return t;if(i(t))return function(t,e){for(var r=-1,n=null==t?0:t.length,a=Array(n);++r<n;)a[r]=e(t[r],r,t);return a}(t,rt)+"";if($(t))return et?et.call(t):"";var e=t+"";return"0"==e&&1/t==-1/0?"-0":e}function nt(t){return null==t?"":rt(t)}function at(t,e){return i(t)?t:function(t,e){if(i(t))return!1;var r=typeof t;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=t&&!$(t))||S.test(t)||!b.test(t)||null!=e&&t in Object(e)}(t,e)?[t]:Q(nt(t))}function st(t){if("string"==typeof t||$(t))return t;var e=t+"";return"0"==e&&1/t==-1/0?"-0":e}function it(t,e,r){var n=null==t?void 0:function(t,e){for(var r=0,n=(e=at(e,t)).length;null!=t&&r<n;)t=t[st(e[r++])];return r&&r==n?t:void 0}(t,e);return void 0===n?r:n}function ot(t){return"[object String]"===Object.prototype.toString.call(t)}function lt(t){return!(!ot(t)||""===t)}function ut(t){return"[object Array]"===Object.prototype.toString.call(t)}function ct(t){return"[object Object]"===Object.prototype.toString.call(t)}function ht(t){return t!=t}function ft(t){return!!function(t){return"[object Undefined]"===Object.prototype.toString.call(t)}(t)||(!!function(t){return"[object Null]"===Object.prototype.toString.call(t)}(t)||(!!function(t){if(ct(t)){for(let e in t)return!1;return!0}return!1}(t)||(!!function(t){return!(!ot(t)||""!==t)}(t)||(!!function(t){return!!ut(t)&&0===t.length}(t)||!!ht(t)))))}function gt(t){return!!ut(t)&&(0!==t.length&&(1!==t.length||!ft(t[0])))}function dt(t){return!0===(e=t)||!1===e||y(e)&&"[object Boolean]"==w(e);var e}var mt=/\s/;var pt=/^\s+/;function wt(t){return t?t.slice(0,function(t){for(var e=t.length;e--&&mt.test(t.charAt(e)););return e}(t)+1).replace(pt,""):t}var yt=/^[-+]0x[0-9a-f]+$/i,$t=/^0b[01]+$/i,bt=/^0o[0-7]+$/i,St=parseInt;function _t(t){if("number"==typeof t)return t;if($(t))return NaN;if(_(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=_(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=wt(t);var r=$t.test(t);return r||bt.test(t)?St(t.slice(2),r?2:8):yt.test(t)?NaN:+t}var vt=1/0;function Dt(t){return t?(t=_t(t))===vt||t===-1/0?17976931348623157e292*(t<0?-1:1):t==t?t:0:0===t?t:0}function Mt(t){var e=Dt(t),r=e%1;return e==e?r?e-r:e:0}function At(t){let e=!1;if(lt(t))e=!isNaN(Number(t));else if(function(t){return"[object Number]"===Object.prototype.toString.call(t)}(t)){if(ht(t))return!1;e=!0}return e}function xt(t){if(!At(t))return 0;return Dt(t)}function Tt(t){return!!At(t)&&(t=xt(t),"number"==typeof(e=t)&&e==Mt(e));var e}var Et=u.isFinite,Ot=Math.min;var kt=function(t){var e=Math[t];return function(t,r){if(t=_t(t),(r=null==r?0:Ot(Mt(r),292))&&Et(t)){var n=(nt(t)+"e").split("e");return+((n=(nt(e(n[0]+"e"+(+n[1]+r)))+"e").split("e"))[0]+"e"+(+n[1]-r))}return e(t)}}("round"),jt=kt;function Lt(t){if(!At(t))return 0;t=xt(t);let e=jt(t);return"0"===String(e)?0:e}function Pt(t){if(!Tt(t))return!1;return Lt(t)>0}function Ct(t){if(!Tt(t))return!1;return Lt(t)>=0}function It(t=10){At(t)||(t=10);let e=function(){let t,e,r=new Promise(function(){t=arguments[0],e=arguments[1]});return r.resolve=t,r.reject=e,r}();return setTimeout(function(){e.resolve()},t),e}let Ut=["ECONNRESET","ETIMEDOUT","ENOTFOUND","ECONNREFUSED","ECONNABORTED","EAI_AGAIN","EPIPE"];function Rt(t,e){let r=it(t,"statusCode",null);if(Pt(r))return r>=500||429===r||e.includes(r);let n=it(t,"cause.code",null)||it(t,"code",null);if(lt(n)&&Ut.includes(n))return!0;let a=it(t,"name","");if("AbortError"===a||"TimeoutError"===a)return!0;let s=String(it(t,"cause.code","")||it(t,"message",""));return!!/UND_ERR_(SOCKET|HEADERS_TIMEOUT|BODY_TIMEOUT|CONNECT_TIMEOUT)/.test(s)}async function Yt(t,e){let r=new AbortController,n=setTimeout(()=>{r.abort()},e.timeout);try{let n={method:e.method,headers:e.headers,signal:r.signal};null!==e.body&&(n.body=e.body);let a=await fetch(t,n);if(!a.ok){let e=new Error(`HTTP ${a.status} ${a.statusText}`.trim());throw e.statusCode=a.status,e.url=t,e}if("arrayBuffer"===e.responseType)return await a.arrayBuffer();if("text"===e.responseType){if(""!==e.encoding){let t=await a.arrayBuffer();return new TextDecoder(e.encoding).decode(t)}return await a.text()}let s=await a.text();try{return JSON.parse(s)}catch(t){throw new Error(`回傳非JSON格式(可能為維護頁面或網路中介): ${s.slice(0,100)}`)}}finally{clearTimeout(n)}}async function Nt(t,e={}){if(!lt(t))throw new Error(`url須為有效字串, 得到: ${t}`);let r=it(e,"method");lt(r)||(r="GET");let n=it(e,"headers");ct(n)||(n={}),lt(it(n,"User-Agent"))||(n={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",...n});let a=it(e,"body",null);lt(a)||(a=null);let s=it(e,"responseType");"text"!==s&&"arrayBuffer"!==s&&(s="json");let i=it(e,"encoding");lt(i)||(i="");let o=it(e,"timeout");o=Pt(o)?Lt(o):3e4;let l=it(e,"maxRetries");l=Ct(l)?Lt(l):10;let u=it(e,"baseDelayMs");u=Pt(u)?Lt(u):5e3;let c=it(e,"maxDelayMs");c=Pt(c)?Lt(c):3e4;let h=it(e,"retryStatus");gt(h)||(h=[]);let f=it(e,"label");lt(f)||(f="fetchWithRetry");let g=it(e,"showLog");dt(g)||(g=!0);let d={method:r,headers:n,body:a,responseType:s,encoding:i,timeout:o},m=null;for(let e=1;e<=l+1;e++)try{return await Yt(t,d)}catch(t){m=t;let r=l+1-e;if(!Rt(t,h)||r<=0)throw t;let n=Math.min(u*e,c);g&&console.warn(`[${f}][Retry ${e}/${l}] ${t.message} — 等待 ${n/1e3}s 後重試...`),await It(n)}throw m}function Ft(t,e,r,n){let a=it(t,r);return Pt(a)?Lt(a):(a=it(e,r),Pt(a)?Lt(a):n)}function Ht(t={},e={}){let r=it(t,"maxRetries");Ct(r)||(r=it(e,"maxRetries")),r=Ct(r)?Lt(r):10;let n=it(t,"showLog");dt(n)||(n=it(e,"showLog")),dt(n)||(n=!0);let a={timeout:Ft(t,e,"timeout",3e4),maxRetries:r,baseDelayMs:Ft(t,e,"baseDelayMs",5e3),maxDelayMs:Ft(t,e,"maxDelayMs",3e4),showLog:n};return lt(it(e,"responseType"))&&(a.responseType=e.responseType),lt(it(e,"encoding"))&&(a.encoding=e.encoding),ct(it(e,"headers"))&&(a.headers=e.headers),gt(it(e,"retryStatus"))&&(a.retryStatus=e.retryStatus),lt(it(e,"label"))&&(a.label=e.label),a}"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function qt(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var zt={exports:{}};!function(t){t.exports=function(){var t=1e3,e=6e4,r=36e5,n="millisecond",a="second",s="minute",i="hour",o="day",l="week",u="month",c="quarter",h="year",f="date",g="Invalid Date",d=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,m=/\[([^\]]+)]|YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,p={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],r=t%100;return"["+t+(e[(r-20)%10]||e[r]||e[0])+"]"}},w=function(t,e,r){var n=String(t);return!n||n.length>=e?t:""+Array(e+1-n.length).join(r)+t},y={s:w,z:function(t){var e=-t.utcOffset(),r=Math.abs(e),n=Math.floor(r/60),a=r%60;return(e<=0?"+":"-")+w(n,2,"0")+":"+w(a,2,"0")},m:function t(e,r){if(e.date()<r.date())return-t(r,e);var n=12*(r.year()-e.year())+(r.month()-e.month()),a=e.clone().add(n,u),s=r-a<0,i=e.clone().add(n+(s?-1:1),u);return+(-(n+(r-a)/(s?a-i:i-a))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(t){return{M:u,y:h,w:l,d:o,D:f,h:i,m:s,s:a,ms:n,Q:c}[t]||String(t||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},$="en",b={};b[$]=p;var S="$isDayjsObject",_=function(t){return t instanceof A||!(!t||!t[S])},v=function t(e,r,n){var a;if(!e)return $;if("string"==typeof e){var s=e.toLowerCase();b[s]&&(a=s),r&&(b[s]=r,a=s);var i=e.split("-");if(!a&&i.length>1)return t(i[0])}else{var o=e.name;b[o]=e,a=o}return!n&&a&&($=a),a||!n&&$},D=function(t,e){if(_(t))return t.clone();var r="object"==typeof e?e:{};return r.date=t,r.args=arguments,new A(r)},M=y;M.l=v,M.i=_,M.w=function(t,e){return D(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var A=function(){function p(t){this.$L=v(t.locale,null,!0),this.parse(t),this.$x=this.$x||t.x||{},this[S]=!0}var w=p.prototype;return w.parse=function(t){this.$d=function(t){var e=t.date,r=t.utc;if(null===e)return new Date(NaN);if(M.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var n=e.match(d);if(n){var a=n[2]-1||0,s=(n[7]||"0").substring(0,3);return r?new Date(Date.UTC(n[1],a,n[3]||1,n[4]||0,n[5]||0,n[6]||0,s)):new Date(n[1],a,n[3]||1,n[4]||0,n[5]||0,n[6]||0,s)}}return new Date(e)}(t),this.init()},w.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},w.$utils=function(){return M},w.isValid=function(){return!(this.$d.toString()===g)},w.isSame=function(t,e){var r=D(t);return this.startOf(e)<=r&&r<=this.endOf(e)},w.isAfter=function(t,e){return D(t)<this.startOf(e)},w.isBefore=function(t,e){return this.endOf(e)<D(t)},w.$g=function(t,e,r){return M.u(t)?this[e]:this.set(r,t)},w.unix=function(){return Math.floor(this.valueOf()/1e3)},w.valueOf=function(){return this.$d.getTime()},w.startOf=function(t,e){var r=this,n=!!M.u(e)||e,c=M.p(t),g=function(t,e){var a=M.w(r.$u?Date.UTC(r.$y,e,t):new Date(r.$y,e,t),r);return n?a:a.endOf(o)},d=function(t,e){return M.w(r.toDate()[t].apply(r.toDate("s"),(n?[0,0,0,0]:[23,59,59,999]).slice(e)),r)},m=this.$W,p=this.$M,w=this.$D,y="set"+(this.$u?"UTC":"");switch(c){case h:return n?g(1,0):g(31,11);case u:return n?g(1,p):g(0,p+1);case l:var $=this.$locale().weekStart||0,b=(m<$?m+7:m)-$;return g(n?w-b:w+(6-b),p);case o:case f:return d(y+"Hours",0);case i:return d(y+"Minutes",1);case s:return d(y+"Seconds",2);case a:return d(y+"Milliseconds",3);default:return this.clone()}},w.endOf=function(t){return this.startOf(t,!1)},w.$set=function(t,e){var r,l=M.p(t),c="set"+(this.$u?"UTC":""),g=(r={},r[o]=c+"Date",r[f]=c+"Date",r[u]=c+"Month",r[h]=c+"FullYear",r[i]=c+"Hours",r[s]=c+"Minutes",r[a]=c+"Seconds",r[n]=c+"Milliseconds",r)[l],d=l===o?this.$D+(e-this.$W):e;if(l===u||l===h){var m=this.clone().set(f,1);m.$d[g](d),m.init(),this.$d=m.set(f,Math.min(this.$D,m.daysInMonth())).$d}else g&&this.$d[g](d);return this.init(),this},w.set=function(t,e){return this.clone().$set(t,e)},w.get=function(t){return this[M.p(t)]()},w.add=function(n,c){var f,g=this;n=Number(n);var d=M.p(c),m=function(t){var e=D(g);return M.w(e.date(e.date()+Math.round(t*n)),g)};if(d===u)return this.set(u,this.$M+n);if(d===h)return this.set(h,this.$y+n);if(d===o)return m(1);if(d===l)return m(7);var p=(f={},f[s]=e,f[i]=r,f[a]=t,f)[d]||1,w=this.$d.getTime()+n*p;return M.w(w,this)},w.subtract=function(t,e){return this.add(-1*t,e)},w.format=function(t){var e=this,r=this.$locale();if(!this.isValid())return r.invalidDate||g;var n=t||"YYYY-MM-DDTHH:mm:ssZ",a=M.z(this),s=this.$H,i=this.$m,o=this.$M,l=r.weekdays,u=r.months,c=r.meridiem,h=function(t,r,a,s){return t&&(t[r]||t(e,n))||a[r].slice(0,s)},f=function(t){return M.s(s%12||12,t,"0")},d=c||function(t,e,r){var n=t<12?"AM":"PM";return r?n.toLowerCase():n};return n.replace(m,function(t,n){return n||function(t){switch(t){case"YY":return String(e.$y).slice(-2);case"YYYY":return M.s(e.$y,4,"0");case"M":return o+1;case"MM":return M.s(o+1,2,"0");case"MMM":return h(r.monthsShort,o,u,3);case"MMMM":return h(u,o);case"D":return e.$D;case"DD":return M.s(e.$D,2,"0");case"d":return String(e.$W);case"dd":return h(r.weekdaysMin,e.$W,l,2);case"ddd":return h(r.weekdaysShort,e.$W,l,3);case"dddd":return l[e.$W];case"H":return String(s);case"HH":return M.s(s,2,"0");case"h":return f(1);case"hh":return f(2);case"a":return d(s,i,!0);case"A":return d(s,i,!1);case"m":return String(i);case"mm":return M.s(i,2,"0");case"s":return String(e.$s);case"ss":return M.s(e.$s,2,"0");case"SSS":return M.s(e.$ms,3,"0");case"Z":return a}return null}(t)||a.replace(":","")})},w.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},w.diff=function(n,f,g){var d,m=this,p=M.p(f),w=D(n),y=(w.utcOffset()-this.utcOffset())*e,$=this-w,b=function(){return M.m(m,w)};switch(p){case h:d=b()/12;break;case u:d=b();break;case c:d=b()/3;break;case l:d=($-y)/6048e5;break;case o:d=($-y)/864e5;break;case i:d=$/r;break;case s:d=$/e;break;case a:d=$/t;break;default:d=$}return g?d:M.a(d)},w.daysInMonth=function(){return this.endOf(u).$D},w.$locale=function(){return b[this.$L]},w.locale=function(t,e){if(!t)return this.$L;var r=this.clone(),n=v(t,e,!0);return n&&(r.$L=n),r},w.clone=function(){return M.w(this.$d,this)},w.toDate=function(){return new Date(this.valueOf())},w.toJSON=function(){return this.isValid()?this.toISOString():null},w.toISOString=function(){return this.$d.toISOString()},w.toString=function(){return this.$d.toUTCString()},p}(),x=A.prototype;return D.prototype=x,[["$ms",n],["$s",a],["$m",s],["$H",i],["$W",o],["$M",u],["$y",h],["$D",f]].forEach(function(t){x[t[1]]=function(e){return this.$g(e,t[0],t[1])}}),D.extend=function(t,e){return t.$i||(t(e,A,D),t.$i=!0),D},D.locale=v,D.isDayjs=_,D.unix=function(t){return D(1e3*t)},D.en=b[$],D.Ls=b,D.p={},D}()}(zt);var Wt=qt(zt.exports),Bt={exports:{}};!function(t){var e,r,n;t.exports=(e="minute",r=/[+-]\d\d(?::?\d\d)?/g,n=/([+-]|\d\d)/g,function(t,a,s){var i=a.prototype;s.utc=function(t){return new a({date:t,utc:!0,args:arguments})},i.utc=function(t){var r=s(this.toDate(),{locale:this.$L,utc:!0});return t?r.add(this.utcOffset(),e):r},i.local=function(){return s(this.toDate(),{locale:this.$L,utc:!1})};var o=i.parse;i.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),o.call(this,t)};var l=i.init;i.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds()}else l.call(this)};var u=i.utcOffset;i.utcOffset=function(t,a){var s=this.$utils().u;if(s(t))return this.$u?0:s(this.$offset)?u.call(this):this.$offset;if("string"==typeof t&&(t=function(t){void 0===t&&(t="");var e=t.match(r);if(!e)return null;var a=(""+e[0]).match(n)||["-",0,0],s=a[0],i=60*+a[1]+ +a[2];return 0===i?0:"+"===s?i:-i}(t),null===t))return this;var i=Math.abs(t)<=16?60*t:t;if(0===i)return this.utc(a);var o=this.clone();if(a)return o.$offset=i,o.$u=!1,o;var l=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();return(o=this.local().add(i+l,e)).$offset=i,o.$x.$localOffset=l,o};var c=i.format;i.format=function(t){var e=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return c.call(this,e)},i.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*t},i.isUTC=function(){return!!this.$u},i.toISOString=function(){return this.toDate().toISOString()},i.toString=function(){return this.toDate().toUTCString()};var h=i.toDate;i.toDate=function(t){return"s"===t&&this.$offset?s(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():h.call(this)};var f=i.diff;i.diff=function(t,e,r){if(t&&this.$u===t.$u)return f.call(this,t,e,r);var n=this.local(),a=s(t).local();return f.call(n,a,e,r)}})}(Bt);var Kt=qt(Bt.exports);Wt.extend(Kt);function Gt(t){let e=null;if(At(t)){if(0===t)return"";e=Wt.unix(t)}else if(t instanceof Date)e=Wt(t);else{if(!lt(t))return"";e=Wt(t)}return e.isValid()?e.utcOffset(8).format("YYYY-MM-DD HH:mm:ss"):""}let Xt={timeout:3e4,maxRetries:5,baseDelayMs:3e3,maxDelayMs:15e3,label:"fetch-ai-news-aggregator"};let Zt={nbsp:" ",amp:"&",lt:"<",gt:">",quot:'"',apos:"'",hellip:"…",mdash:"—",ndash:"–",middot:"·",ldquo:'"',rdquo:'"',lsquo:"‘",rsquo:"’",laquo:"«",raquo:"»",copy:"©",reg:"®"};function Jt(t){return ot(t)?t.replace(/&([a-zA-Z]+);/g,(t,e)=>void 0!==Zt[e]?Zt[e]:t).replace(/&#(\d+);/g,(t,e)=>String.fromCodePoint(Number(e))).replace(/&#x([\da-fA-F]+);/g,(t,e)=>String.fromCodePoint(parseInt(e,16))):""}function Vt(t,e={}){if(!ot(t))return"";let r=it(e,"image");dt(r)||(r=!1);let n=t;return n=n.replace(/<script[\s\S]*?<\/script>/gi,""),n=n.replace(/<style[\s\S]*?<\/style>/gi,""),n=n.replace(/<!--[\s\S]*?-->/g,""),r&&(n=n.replace(/<img[^>]*src="([^"]+)"[^>]*\/?>/gi,(t,e)=>``)),n=n.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi,"\n\n**$1**\n\n"),n=n.replace(/<\/?(?:strong|b)(?:\s[^>]*)?>/gi,"**"),n=n.replace(/<\/?(?:em|i)(?:\s[^>]*)?>/gi,"*"),n=n.replace(/<br\s*\/?>/gi,"\n"),n=n.replace(/<a\s+[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi,(t,e,r)=>{let n=r.replace(/<[^>]+>/g,"").trim();return n?`[${n}](${e})`:""}),n=n.replace(/<p(?:\s[^>]*)?>/gi,""),n=n.replace(/<\/p>/gi,"\n\n"),n=n.replace(/<\/?(?:div|span|section|article|font|u)(?:\s[^>]*)?>/gi,""),n=n.replace(/<[^>]+>/g,""),n=Jt(n),n=n.split("\n").map(t=>t.replace(/[ \t\u00A0]+/g," ").trimEnd()).join("\n"),n=n.replace(/\n{3,}/g,"\n\n").replace(/\*\*\s*\*\*/g,""),n.trim()}function Qt(t,e){if(!ot(t))return null;if(!lt(e))return null;let r=`<div class="${e}"`,n=t.indexOf(r);if(-1===n)return null;let a=t.indexOf(">",n);if(-1===a)return null;let s=a+1,i=s,o=1;for(;o>0&&i<t.length;){let e=t.indexOf("<div",i),r=t.indexOf("</div>",i);if(-1===r)return null;if(-1!==e&&e<r)o++,i=e+4;else{if(o--,0===o)return t.slice(s,r);i=r+6}}return null}function te(t){return ot(t)||(t=String(null==t?"":t)),t.replace(/[\\/:*?"<>|]/g,"_").slice(0,200)}let ee="https://www.aisixiang.com",re="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36",ne={responseType:"text",headers:{"User-Agent":re,"Accept-Language":"zh-CN,zh;q=0.9"},timeout:3e4,maxRetries:5,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-aisixiang"};function ae(t={}){let e=it(t,"baseUrl");lt(e)||(e=ee),e=e.replace(/\/+$/,"");let r=Ht(t,ne),n=it(t,"pageDelayMs");n=Ct(n)?Lt(n):1e3;let a=it(t,"maxPages");return a=Pt(a)?Math.min(Lt(a),50):50,{baseUrl:e,showLog:r.showLog,pageDelayMs:n,maxPages:a,optFetch:r}}function se(t,e){let r=Qt(t,"search_list");if(null===r)return[];let n,a=[],s=new Set,i=/href="\/data\/(\d+)\.html"[^>]*title="([^"]+)"/g;for(;null!==(n=i.exec(r));){if(s.has(n[1]))continue;s.add(n[1]);let t=n[2],r=t.indexOf(":");a.push({aid:n[1],url:`${e}/data/${n[1]}.html`,title:r>0?t.slice(r+1).trim():t.trim(),author:r>0?t.slice(0,r).trim():""})}return a}function ie(t,e){let r,n=t.match(/([\s\S]+?)<div class="list_page">/),a=n?n[1]:t,s=[],i=new Set,o=/<a\s+href="\/data\/(\d+)\.html"[^>]*>([^<]+)<\/a>/g;for(;null!==(r=o.exec(a));){if(i.has(r[1]))continue;i.add(r[1]);let t=r[2].trim(),n=t.indexOf(":");s.push({aid:r[1],url:`${e}/data/${r[1]}.html`,title:n>0?t.slice(n+1).trim():t,author:n>0?t.slice(0,n).trim():""})}return s}function oe(t){let e=t.match(/<div class="list_page">([\s\S]+?)<\/div>/);if(!e)return 1;let r=[...e[1].matchAll(/page=(\d+)/g)].map(t=>Number(t[1]));return r.length?Math.max(...r):1}async function le(t,e,r){let n=[],a=new Set,s=1,i=0;for(let o=1;o<=r.maxPages;o++){let l=t(o);r.showLog&&process.stderr.write(`[info] fetching page ${o}${s>1?`/${s}`:""} ...\n`);let u=await Nt(l,r.optFetch);i++,1===o&&(s=oe(u));let c=e(u,r.baseUrl).filter(t=>!a.has(t.aid));if(0===c.length){r.showLog&&process.stderr.write(`[info] page ${o} 無新項目,停止翻頁\n`);break}for(let t of c)a.add(t.aid);if(n.push(...c),o>=s)break;await It(r.pageDelayMs)}return{items:n,total_pages:s,pages_fetched:i}}async function ue(t={}){let e=ae(t),r=[...(await Nt(`${e.baseUrl}/thinktank/`,e.optFetch)).matchAll(/href="\/thinktank\/([^"]+?)\.html"[^>]*>([^<]+)</g)],n=new Map;for(let[,t,e]of r){let r=t.toLowerCase();if(n.has(r))continue;let a=e.split(/[,、]/)[0].trim();!a||a.length>30||n.set(r,{slug:t,name:a})}return[...n.values()]}function ce(t,e){return Array.isArray(t)&&t.find(t=>t.name===e)||null}async function he(t={}){let e=ae(t),r=await Nt(`${e.baseUrl}/zhuanti/`,e.optFetch),n=[{name:"学科",start:r.indexOf("<h3>学科关键词</h3>")},{name:"事件",start:r.indexOf("<h3>事件关键词</h3>")},{name:"人物",start:r.indexOf("<h3>人物关键词</h3>")}].filter(t=>t.start>=0).sort((t,e)=>t.start-e.start);n.push({name:null,start:r.length});let a=new Set,s=[];for(let t=0;t<n.length-1;t++){let e,i=r.slice(n[t].start,n[t+1].start),o=/href="\/zhuanti\/(\d+)\.html"[^>]*>([^<]+)</g;for(;null!==(e=o.exec(i));){if(a.has(e[1]))continue;a.add(e[1]);let r=e[2].trim().replace(/^\[|\]$/g,"").trim();!r||r.length>50||s.push({id:e[1],name:r,category:n[t].name})}}return s}function fe(t,e){return Array.isArray(t)&&t.find(t=>t.name===e)||null}let ge={BASE_URL:ee,USER_AGENT:re,MAX_PAGES:50,safeFilename:te,fetchAuthorsList:ue,lookupAuthor:ce,fetchTopicsList:he,lookupTopic:fe,fetchAuthorArticles:async function(t={}){let e=ae(t),r=it(t,"name");lt(r)||(r="");let n=it(t,"slug");if(lt(n)||(n=""),!r&&!n)throw new Error("需要 name 或 slug");let a=null,s=r,i=n;if(i)a={slug:i,url:`${e.baseUrl}/thinktank/${i}.html`};else{e.showLog&&process.stderr.write("[info] fetching authors list from /thinktank/ ...\n");let n=await ue(t),o=ce(n,r);if(!o)return{status:"success",site:"aisixiang",mode:"author",query:r,fetched_at:(new Date).toISOString(),authors_count:n.length,count:0,items:[],message:`"${r}" 不在愛思想專欄作者清單中(共 ${n.length} 位)。尚無此作者文章(此為查詢成功的真實結果、0 筆,非抓取失敗)。提醒:本函數不轉簡繁,呼叫端負責用站方登錄字形(通常是簡體)。`};i=o.slug,s=s||o.name,a={slug:i,url:`${e.baseUrl}/thinktank/${i}.html`,name:o.name}}let o=function(t,e){let r=["论文","时评","随笔","著作","演讲","读书","访谈","未分类"],n=[];for(let e of r){let r,a=new RegExp(`<h3>${e}<\\/h3>`,"g");for(;null!==(r=a.exec(t));)n.push({cat:e,pos:r.index})}n.sort((t,e)=>t.pos-e.pos),n.push({cat:null,pos:t.length});let a=[];for(let r=0;r<n.length-1;r++){let s,{cat:i,pos:o}=n[r],l=t.slice(o,n[r+1].pos),u=/<a\s+href="\/data\/(\d+)\.html"[^>]*>([\s\S]*?)<\/a>/g;for(;null!==(s=u.exec(l));){let t=s[2].replace(/<[^>]+>/g,"").trim();t&&a.push({aid:s[1],url:`${e}/data/${s[1]}.html`,title:t,category:i})}}return a}(await Nt(a.url,e.optFetch),e.baseUrl),l={status:"success",site:"aisixiang",mode:"author",query:s||i,resolved:a,fetched_at:(new Date).toISOString(),count:o.length,items:o};return 0===o.length&&(l.message=`已命中愛思想作者欄頁(${a.url})但該欄頁目前 0 篇文章。此為查詢成功的真實結果(非抓取失敗);若 slug 為手動指定,請確認 slug 是否正確。`),l},fetchKeywordArticles:async function(t,e={}){if(!lt(t))throw new Error("需要 keyword");let r=ae(e),n=e=>`${r.baseUrl}/data/search?searchfield=keywords&keywords=${encodeURIComponent(t)}&page=${e}`,{items:a,total_pages:s,pages_fetched:i}=await le(n,se,r);return 0===a.length?{status:"success",site:"aisixiang",mode:"keyword",query:t,resolved:{search_url:n(1)},fetched_at:(new Date).toISOString(),count:0,items:[],message:`關鍵字 "${t}" 在愛思想無相關文章。提醒:站方搜尋只認簡體,呼叫端負責簡體化;若已是簡體仍 0 筆,該主題可能無 tag 索引。`}:{status:"success",site:"aisixiang",mode:"keyword",query:t,resolved:{search_url:n(1),total_pages:s,pages_fetched:i},fetched_at:(new Date).toISOString(),count:a.length,items:a}},fetchTitleArticles:async function(t,e={}){if(!lt(t))throw new Error("需要 keyword");let r=ae(e),n=e=>`${r.baseUrl}/data/search?searchfield=title&keywords=${encodeURIComponent(t)}&page=${e}`,{items:a,total_pages:s,pages_fetched:i}=await le(n,se,r);return 0===a.length?{status:"success",site:"aisixiang",mode:"title",query:t,resolved:{search_url:n(1)},fetched_at:(new Date).toISOString(),count:0,items:[],message:`標題關鍵字 "${t}" 在愛思想無相關文章。提醒:站方搜尋只認簡體,呼叫端負責簡體化。`}:{status:"success",site:"aisixiang",mode:"title",query:t,resolved:{search_url:n(1),total_pages:s,pages_fetched:i},fetched_at:(new Date).toISOString(),count:a.length,items:a}},fetchTopicArticles:async function(t={}){let e=ae(t),r=it(t,"keyword");lt(r)||(r="");let n=it(t,"id");if(lt(n)||(n=""),!r&&!n)throw new Error("需要 keyword 或 id");let a=n,s=r,i=null;if(a)i={id:a,url:`${e.baseUrl}/zhuanti/${a}.html`};else{e.showLog&&process.stderr.write("[info] fetching topics list from /zhuanti/ ...\n");let n=await he(t),o=fe(n,r);if(!o)return{status:"success",site:"aisixiang",mode:"topic",query:r,fetched_at:(new Date).toISOString(),topics_count:n.length,count:0,items:[],message:`"${r}" 不在愛思想策展主題清單中(共 ${n.length} 個主題)。建議改用 fetchKeywordArticles('${r}') 查 keyword tag 結果。本函數不自動轉向,請呼叫端決定是否重試。`};a=o.id,s=o.name,i={id:a,name:o.name,category:o.category,url:`${e.baseUrl}/zhuanti/${a}.html`}}let{items:o,total_pages:l,pages_fetched:u}=await le(t=>`${e.baseUrl}/zhuanti/${a}.html?page=${t}`,ie,e),c={status:"success",site:"aisixiang",mode:"topic",query:s||a,resolved:{...i,total_pages:l,pages_fetched:u},fetched_at:(new Date).toISOString(),count:o.length,items:o};return 0===o.length&&(c.message=`已命中愛思想策展主題(${i.url})但該主題頁目前 0 篇文章。此為查詢成功的真實結果(非抓取失敗);若需更多結果可改用 fetchKeywordArticles 查 keyword tag。`),c},fetchArticle:async function(t={}){let e=ae(t),r=it(t,"aid");lt(r)||(r="");let n=it(t,"url");lt(n)||(n="");let a=r?`${e.baseUrl}/data/${r}.html`:n;if(!a)throw new Error("需要 aid 或 url");let s=await Nt(a,e.optFetch),i=function(t){let e=t.match(/<title>([\s\S]*?)<\/title>/i);return e?Jt(e[1].trim()):null}(s),o=Qt(s,"article-content");if(!i||!o)throw new Error(`解析失敗:title=${!!i}, content=${!!o}`);let l=Vt(o),u=i.replace(/[_\-\s]*(?:_)?(?:爱思想|愛思想)\s*$/u,"").trim(),c=`---\ntitle: "${u}"\nsource: "${a}"\nauthor:\npublished:\ncreated: ${(new Date).toISOString().slice(0,10)}\ndescription:\n---\n${l}\n`;return{status:"success",site:"aisixiang",mode:"fetch",url:a,title:u,chars:l.length,markdown:c}}},de={timeout:3e4,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,retryStatus:[403],label:"fetch-cnyes"};let{fetchWebByCurl:me}=t,pe="https://www.guancha.cn",we="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",ye=[{slug:"CaiJing",name:"财经"},{slug:"economy",name:"财经-大頻道"},{slug:"JunShi",name:"军事"},{slug:"military-affairs",name:"军事-大頻道"},{slug:"ZhengZhi",name:"政治"},{slug:"WenHua",name:"文化"},{slug:"chanjing",name:"产经"},{slug:"qiche",name:"观出行"},{slug:"gongye-keji",name:"科技"},{slug:"ChengShi",name:"城事"},{slug:"GuanJinRong",name:"观金融"},{slug:"XinShiDai",name:"新时代"},{slug:"ChaoJiGongCheng",name:"超级工程"},{slug:"NengYuanZhanLue",name:"能源战略"},{slug:"RenGongZhiNeng",name:"人工智能"},{slug:"XinZhiGuanChaSuoNews",name:"心智观察所"},{slug:"YiLangJuShi",name:"伊朗局势"},{slug:"MeiGuoMeng",name:"美国一梦"},{slug:"MeiGuoJingJi",name:"美国经济"},{slug:"ELuoSiZhiSheng",name:"俄罗斯之声"},{slug:"lianganyuanzhuopai",name:"两岸圆桌派"},{slug:"ZheJiuShiZhongGuo",name:"这就是中国"},{slug:"YiZhouJunQingGuanCha",name:"一周军事观察"},{slug:"feizhoushangkou",name:"非洲之窗"},{slug:"toutiao",name:"观察者头条"},{slug:"gushi",name:"股市"},{slug:"guanwangwenyu",name:"新潮观鱼"},{slug:"jingtiriben",name:"冲破战后秩序 日本想干什么"},{slug:"DaoGuoDianAVI",name:"日本"}];function $e(t={}){let e=it(t,"baseUrl");lt(e)||(e=pe),e=e.replace(/\/+$/,"");let r=it(t,"maxRetries");r=Ct(r)?Lt(r):5;let n=it(t,"timeout");n=Pt(n)?Lt(n):null;let a=it(t,"showLog");dt(a)||(a=!0);let s={userAgent:we,referer:e,maxRetries:r};null!==n&&(s.timeoutMs=n);let i=it(t,"pageDelayMs");i=Ct(i)?Lt(i):1e3;let o=it(t,"maxPages");return o=Pt(o)?Math.min(Lt(o),50):50,{baseUrl:e,showLog:a,pageDelayMs:i,maxPages:o,optFetch:s}}async function be(t,e){let r=await me(t,e.optFetch);if("success"!==r.status){let e=new Error(r.message||"fetch failed");throw e.reason=r.reason,e.url=t,e.httpCode=r.httpCode,e}return r.html}function Se(t){let e=t.match(/<dt>[A-Z]<\/dt>/g);return!!e&&e.length>=5}function _e(t,e){let r,n=t.replace(/\n/g," "),a=[],s=new Set,i=/<a[^>]+href="\/([A-Za-z][a-zA-Z0-9_-]+)\/(\d{4}_\d{2}_\d{2}_\d+)\.shtml"(?:[^>]*title="([^"]+)")?[^>]*>([^<]*)<\/a>/g;for(;null!==(r=i.exec(n));){let t=r[1],n=r[2],i=r[3]&&Jt(r[3]).trim(),o=r[4]&&Jt(r[4]).replace(/<[^>]+>/g,"").trim(),l=i||o||"",u=`${t}/${n}`;!s.has(u)&&l&&(s.add(u),a.push({url:`${e}/${t}/${n}.shtml`,title:l,slug:t}))}return a}async function ve(t,e){let r=[],n=new Set,a=0;for(let s=1;s<=e.maxPages;s++){let i=`${e.baseUrl}/${t}/list_${s}.shtml`;e.showLog&&process.stderr.write(`[info] fetching ${t} list_${s} ...\n`);let o=null;try{o=await be(i,e)}catch(t){if(1===s)throw t;e.showLog&&process.stderr.write(`[info] page ${s} fetch error (assumed end of list): ${t.message}\n`);break}if(Se(o)){if(1===s){let e=new Error(`slug "${t}" 不存在:請求 list 頁被觀察者網 302 導向首頁。`);throw e.reason="redirected-to-homepage",e}e.showLog&&process.stderr.write(`[info] page ${s} 被導向首頁,視為到底,停止翻頁\n`);break}a++;let l=_e(o,e.baseUrl).filter(t=>!n.has(t.url));if(0===l.length){e.showLog&&process.stderr.write(`[info] page ${s} 無新項目,停止翻頁\n`);break}for(let t of l)n.add(t.url);r.push(...l),s<e.maxPages&&await It(e.pageDelayMs)}return{items:r,pages_fetched:a}}async function De(t={}){let e=$e(t);return function(t){let e=t.replace(/\n/g," "),r=[],n=new Set,a=[...e.matchAll(/<dt>([A-Z])<\/dt>\s*<dd[^>]*>([\s\S]*?)<\/dd>/g)];for(let[,t,e]of a){let a,s=/<a[^>]+href="(?:\.\.\/|\/)?([A-Za-z][a-zA-Z0-9_-]+)\/list_1\.shtml"[^>]*>([^<]+)<\/a>/g;for(;null!==(a=s.exec(e));){let e=a[1],s=Jt(a[2]).trim();s&&!n.has(e)&&(n.add(e),r.push({slug:e,name:s,letter:t}))}}return r}(await be(`${e.baseUrl}/`,e))}function Me(t,e){return Array.isArray(t)&&e&&t.find(t=>t.name===e)||null}function Ae(t){return t&&ye.find(e=>e.name===t)||null}let xe={timeout:3e4,maxRetries:5,baseDelayMs:3e3,maxDelayMs:15e3,label:"fetch-hacker-news"};let Te={responseType:"text",timeout:3e4,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,retryStatus:[403],label:"fetch-moneydj"},Ee=/^(\d{2}\/\d{2}\s+\d{2}:\d{2}|\d{2}:\d{2}|昨\s*\d{2}:\d{2})$/;let Oe=5e3,ke=3e4,je=[{name:"上市",marketKind:"sii"},{name:"上櫃",marketKind:"otc"},{name:"興櫃",marketKind:"rotc"},{name:"公開發行",marketKind:"pub"}];async function Le(t,e,r){let n=null;for(let a=1;a<=r.maxRetries+1;a++){if(n=await t.evaluate(async t=>{try{let e=await fetch(t.url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t.payload)});if(e.status>=500||403===e.status||429===e.status)return{error:`HTTP ${e.status}`,retryable:!0};let r=await e.text();try{let t=JSON.parse(r),e=t&&t.code;return 200!==e&&406!==e?{error:`MOPS code ${e}: ${t&&t.message||""}`,json:t,raw:r.substring(0,500),retryable:!1}:{json:t,raw:r.substring(0,500)}}catch(t){return{error:"Parse Error",raw:r,retryable:!1}}}catch(t){return{error:t.toString(),retryable:!0}}},e),!n.error)return n;let s=!1!==n.retryable,i=r.maxRetries+1-a;if(!s||i<=0)return n;let o=Math.min(r.baseDelayMs*a,r.maxDelayMs);r.showLog&&console.warn(`[${e.name}][Retry ${a}/${r.maxRetries}] ${n.error} — 等待 ${o/1e3}s 後重試...`),await It(o)}return n}let Pe={responseType:"text",timeout:3e4,maxRetries:5,baseDelayMs:3e3,maxDelayMs:15e3,retryStatus:[404],label:"fetch-rss"};let Ce={responseType:"text",timeout:3e4,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,retryStatus:[403],label:"fetch-statementdog"},Ie=".statementdog-news-list-item";function Ue(t){if(!lt(t))return!1;if(!/^\d{8}$/.test(t))return!1;let e=Lt(t.slice(0,4)),r=Lt(t.slice(4,6)),n=Lt(t.slice(6,8)),a=new Date(e,r-1,n);return a.getFullYear()===e&&a.getMonth()===r-1&&a.getDate()===n}let Re={responseType:"text",encoding:"big5",timeout:15e3,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-taifex"},Ye={"外資及陸資":"foreign","外資":"foreign","投信":"trust","自營商":"dealers"};function Ne(t){let e=[],r="",n=!1;for(let a=0;a<t.length;a++){let s=t[a];n?'"'===s?'"'===t[a+1]?(r+='"',a++):n=!1:r+=s:","===s?(e.push(r.trim()),r=""):'"'===s&&0===r.length?n=!0:r+=s}return e.push(r.trim()),e}function Fe(t){let e=t.trim().split("\n").map(t=>t.trim()).filter(t=>t.length>0);if(e.length<2)return[];let r=Ne(e[0]),n=[];for(let t=1;t<e.length;t++){let a=Ne(e[t]),s={};for(let t=0;t<r.length;t++)s[r[t]]=a[t]||"";n.push(s)}return n}function He(t){if(!t||"-"===t||""===t)return null;let e=Number(t.replace(/,/g,""));return isNaN(e)?null:e}function qe(t){if(!lt(t))return"";return`${parseInt(t.substring(0,4),10)-1911}/${t.substring(4,6)}/${t.substring(6,8)}`}let ze={timeout:15e3,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-tpex-3insti"},We=["代號","名稱","外陸資買進股數(不含外資自營商)","外陸資賣出股數(不含外資自營商)","外陸資買賣超股數(不含外資自營商)","外資自營商買進股數","外資自營商賣出股數","外資自營商買賣超股數","外資及陸資買進股數","外資及陸資賣出股數","外資及陸資買賣超股數","投信買進股數","投信賣出股數","投信買賣超股數","自營商買進股數(自行買賣)","自營商賣出股數(自行買賣)","自營商買賣超股數(自行買賣)","自營商買進股數(避險)","自營商賣出股數(避險)","自營商買賣超股數(避險)","自營商買進股數","自營商賣出股數","自營商買賣超股數","三大法人買賣超股數合計"];function Be(t){return"string"!=typeof t?t:parseInt(t.replace(/,/g,""),10)||0}let Ke={timeout:15e3,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-tpex-margin"};let Ge={timeout:15e3,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-tpex-stock"};let Xe={responseType:"text",timeout:3e4,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-tw-data-holiday"};let Ze={timeout:15e3,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-twse-margin"};let Je={timeout:1e4,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-twse-stock"};let Ve={timeout:15e3,maxRetries:10,baseDelayMs:5e3,maxDelayMs:3e4,label:"fetch-twse-t86"};let Qe={fetchRSS:async function(t,e={}){if(!lt(t)||!/^https?:\/\//i.test(t))throw new Error("rssUrl 須為有效的 http/https RSS 網址");let r=await Nt(t,Ht(e,Pe)),a=new n,s=await a.parseString(r),i=String(it(s,"title","")||"").trim(),o=it(s,"items");return gt(o)||(o=[]),o.map(t=>({url:String(it(t,"link","")||"").trim(),time:Gt(it(t,"isoDate","")||it(t,"pubDate","")),title:String(it(t,"title","")||"").trim(),description:String(it(t,"contentSnippet","")||it(t,"summary","")||"").trim(),from:String(it(t,"creator","")||i).trim()}))},fetchHackerNews:async function(t=30,e={}){t=Pt(t)?Lt(t):30;let r=it(e,"apiBase");lt(r)||(r="https://hacker-news.firebaseio.com/v0"),r=r.replace(/\/+$/,"");let n=Ht(e,xe),a=await Nt(`${r}/newstories.json`,n);gt(a)||(a=[]);let s=a.slice(0,t),i=[];for(let t=0;t<s.length;t+=10){let e=s.slice(t,t+10),a=await Promise.allSettled(e.map(t=>Nt(`${r}/item/${t}.json`,n)));for(let t of a)"fulfilled"===t.status&&i.push(t.value)}return i.filter(t=>t&&"story"===t.type).map(t=>({url:String(it(t,"url","")||`https://news.ycombinator.com/item?id=${it(t,"id","")}`).trim(),time:Gt(it(t,"time",0)),title:String(it(t,"title","")||"").trim(),description:"",from:"Hacker News"}))},fetchAiNewsAggregator:async function(t={}){let e=it(t,"url");lt(e)||(e="https://raw.githubusercontent.com/SuYxh/ai-news-aggregator/refs/heads/main/data/latest-24h.json");let r=it(await Nt(e,Ht(t,Xt)),"items");return gt(r)||(r=[]),r.map(t=>({url:String(it(t,"url","")||"").trim(),time:Gt(it(t,"published_at","")),title:String(it(t,"title","")||"").trim(),description:"",from:String(it(t,"source","")||"").trim()}))},fetchCnyes:async function(t={}){let e=it(t,"url");lt(e)||(e="https://api.cnyes.com/media/api/v1/newslist/category/tw_stock");let r=it(t,"targetTotal");r=Pt(r)?Lt(r):100;let n=it(t,"pageLimit");n=Pt(n)?Lt(n):30;let a=it(t,"daysBack");a=Pt(a)?Lt(a):10;let s=it(t,"pageDelayMs");s=Ct(s)?Lt(s):500;let i=Ht(t,de),o=i.showLog,l=Math.floor(Date.now()/1e3),u=l-86400*a;o&&console.log("Starting to fetch Anue (tw_stock) news...");let c=[],h=1;for(;c.length<r;){let t=new URLSearchParams({page:String(h),limit:String(n),isCategoryHeadline:"1",startAt:String(u),endAt:String(l)}),r=it(await Nt(`${e}?${t.toString()}`,i),"items.data");if(!gt(r)){o&&console.log("No more items found.");break}c=c.concat(r),o&&console.log(`Page ${h}: Fetched ${r.length} items. Total so far: ${c.length}`),h++,await It(s)}let f=c.slice(0,r);return o&&console.log(`Total items collected: ${f.length}`),f.map(t=>{let e=Gt(it(t,"publishAt",0));return""===e&&o&&console.warn(`[fetch-cnyes] 時間格式化失敗(newsId=${it(t,"newsId","")}, publishAt=${it(t,"publishAt","")}) — 該筆time退回空字串`),{time:e,title:it(t,"title",""),link:`https://news.cnyes.com/news/id/${it(t,"newsId","")}`}})},fetchMoneydj:async function(t={}){let e=it(t,"baseUrl");lt(e)||(e="https://www.moneydj.com/kmdj/news/newsreallist.aspx?a=mb06&index1=");let r=it(t,"totalPages");r=Pt(r)?Lt(r):50;let n=it(t,"onPageDone");(function(t){let e=Object.prototype.toString.call(t);return"[object Function]"===e||"[object AsyncFunction]"===e})(n)||(n=()=>{});let a=Ht(t,Te),i=a.showLog,o=it(t,"pageDelayMs");o=Pt(o)||0===o?Lt(o):null;let l=new URL(e).origin,u=async t=>{let r=await Nt(`${e}${t}`,{...a,label:`${Te.label} Page ${t}`}),n=s.load(r),i=[];return n("tr").each((t,e)=>{let r=n(e),a=r.find("td").eq(0).text().trim(),s=r.find("td").eq(1).find("a");if(a&&Ee.test(a)&&s.length>0){let t=s.attr("title")||s.text().trim(),e=s.attr("href");if(e){let r=e.startsWith("http")?e:l+e;i.push({time:a,title:t,link:r})}}}),i},c=[];for(let t=1;t<=r;t++){let e=null;try{e=await u(t)}catch(e){i&&console.warn(`[Page ${t}] 抓取失敗,已跳過:${e.message}`);continue}c=c.concat(e),n(t,e.length,r),t<r&&await It(null===o?Math.floor(2e3*Math.random())+1e3:o)}if(0===c.length)throw new Error("抓取到 0 筆新聞,可能是頁面結構改變或 selector 失效,請確認 MoneyDJ 頁面是否正常。");return c},fetchStatementdog:async function(t={}){let e=it(t,"url");lt(e)||(e="https://statementdog.com/news/latest");let r=Ht(t,Ce),n=r.showLog;n&&console.log(`Fetching ${e}...`);let a=await Nt(e,r),i=new URL(e).origin,o=s.load(a),l=o(Ie).length>0,u=l?Ie:'article, .news-item, [class*="news"][class*="item"]',c=l?".statementdog-news-list-item-title":'h2, h3, [class*="title"]',h=l?".statementdog-news-list-item-link":"a[href]",f=l?".statementdog-news-list-item-date":'time, [class*="date"], [class*="time"]';!l&&n&&console.warn("主要 CSS selector 未匹配,嘗試 fallback selector...");let g=[];if(o(u).each((t,e)=>{let r=o(e).find(c),n=l?o(e).find(h):o(e).find(h).first(),a=o(e).find(f),s=r.text().trim(),u=n.attr("href"),d=a.text().trim();s&&u&&(u.startsWith("http")||(u=`${i}${u.startsWith("/")?"":"/"}${u}`),g.push({time:d,title:s,link:u}))}),n&&console.log(`Extracted News Items: ${g.length}`),0===g.length)throw new Error("抓取到 0 筆新聞,可能是頁面結構改變或 selector 失效,請確認財報狗頁面是否正常。");return g},fetchMops:async function(t={}){let e=it(t,"pageUrl");lt(e)||(e="https://mops.twse.com.tw/mops/#/web/t146sb10");let n=it(t,"apiUrl");lt(n)||(n="https://mops.twse.com.tw/mops/api/t146sb10");let a=it(t,"maxRetries");a=Ct(a)?Lt(a):10;let s=it(t,"showLog");dt(s)||(s=!0);let i={maxRetries:a,baseDelayMs:Oe,maxDelayMs:ke,showLog:s},o=je.map(t=>{return{name:t.name,marketKind:t.marketKind,url:n,payload:(e=t.marketKind,{scopeType:"2",companyId:"",dateType:"2",firstDate:"",lastDate:"",marketKind:e,announcementBasis:"0",dateRangeType:"1",announcementType:"1",sort:"1",encodeURIComponent:1,step:1,firstin:1,off:1})};var e});s&&console.log("啟動瀏覽器...");let l=null;for(let t=1;t<=a+1;t++)try{l=await r.chromium.launch({headless:!0,executablePath:process.env.CHROME_PATH||void 0,channel:"chrome",args:["--no-sandbox","--disable-setuid-sandbox"],timeout:36e4});break}catch(e){if(a+1-t<=0)throw new Error(`瀏覽器啟動失敗(已重試 ${a} 次): ${e.message}`);let r=Math.min(Oe*t,ke);s&&console.warn(`[browser.launch][Retry ${t}/${a}] ${e.message} — 等待 ${r/1e3}s 後重試...`),await It(r)}try{let t=await l.newPage();s&&console.log("前往 MOPS 重大訊息頁面 (t146sb10)...");for(let r=1;r<=a+1;r++)try{await t.goto(e,{waitUntil:"networkidle",timeout:6e4});break}catch(t){if(a+1-r<=0)throw t;let e=Math.min(Oe*r,ke);s&&console.warn(`[page.goto][Retry ${r}/${a}] ${t.message} — 等待 ${e/1e3}s 後重試...`),await It(e)}await t.waitForTimeout(2e3);let r=[];for(let e of o){s&&console.log(`正在抓取 [${e.name}] 資料...`);let n=await Le(t,e,i);r.push({market:e.name,marketKind:e.marketKind,data:n.json||n,...n.error&&{error:n.error},timestamp:(new Date).toISOString()}),await t.waitForTimeout(1e3)}if(s){let t=r.map(t=>{let e=it(t,"data.result",it(t,"data")),r=Array.isArray(e)?e.length:0;return`${t.market}: 取得 ${r} 筆資料`});console.log("抓取完成。摘要:",t)}let n=r.some(t=>t.error);return{results:r,hasError:n}}finally{await l.close()}},fetchTwseStock:async function(t,e,r={}){if(!Ue(t))throw new Error(`dateStr 須為合法之 YYYYMMDD 字串,得到: ${t}`);let n=it(r,"baseUrl");lt(n)||(n="https://www.twse.com.tw"),n=n.replace(/\/+$/,"");let a=Ht(r,Je),s=a.showLog,i=lt(e)&&"all"!==e.toLowerCase(),o=i?e:"ALLBUT0999",l="";l=i?`${n}/exchangeReport/STOCK_DAY?response=json&date=${t}&stockNo=${o}`:`${n}/exchangeReport/MI_INDEX?response=json&date=${t}&type=ALLBUT0999`,s&&(console.log(`Fetching TWSE data: ${t}, Stock: ${o}`),console.log(`URL: ${l}`));let u=await Nt(l,a);if("OK"!==it(u,"stat"))throw new Error(`TWSE API returned: ${it(u,"stat")}`);if(i&&Array.isArray(u.data)){let r=qe(t),n=u.data.filter(t=>t[0]===r);if(u.data=n,0===n.length)throw new Error(`TWSE 個股 ${e} 於 ${t} 無交易資料(可能為假日或停盤)`)}return u},fetchTpexStock:async function(t,e,r={}){if(!Ue(t))throw new Error(`dateStr 須為合法之 YYYYMMDD 字串,得到: ${t}`);let n=it(r,"baseUrl");lt(n)||(n="https://www.tpex.org.tw"),n=n.replace(/\/+$/,"");let a=Ht(r,Ge),s=a.showLog,i=gt(e)?e:[],o=qe(t),l=`${n}/web/stock/aftertrading/daily_close_quotes/stk_quote_result.php?l=zh-tw&d=${o}&s=0,asc,0&o=json`;s&&(console.log(`Fetching TPEX data: ${t} (${o})`),console.log(`Target: ${i.length>0?i.join(", "):"All Market"}`),console.log(`URL: ${l}`));let u=await Nt(l,a),c=Array.isArray(u.tables)?u.tables:[],h=c.find(t=>it(t,"title","").includes("行情")&&it(t,"data.length",0)>0)||c.find(t=>it(t,"data.length",0)>0),f=it(h,"data");if(!gt(f))throw new Error("TPEX API returned no data. Possibly a holiday or data not yet available.");if(!Array.isArray(f[0])||f[0].length<8)throw new Error(`TPEX 行情資料欄數不符預期(首列 ${Array.isArray(f[0])?f[0].length:"N/A"} 欄,應 >= 8),可能 API 格式變更`);let g=f;if(i.length>0&&(g=g.filter(t=>i.includes(t[0])),0===g.length))throw new Error(`指定個股 ${i.join(",")} 在 ${t} 之上櫃資料中查無資料(可能為上市股、代碼有誤、或當日無交易)`);return{source:"tpex",date:t,count:g.length,data:g}},fetchTwseMargin:async function(t,e,r={}){if(!lt(t)||!/^\d{8}$/.test(t))throw new Error(`日期參數無效:格式須為 YYYYMMDD (收到 "${t}")`);if(!Ue(t))throw new Error(`日期參數無效:不合法的日期 (${t})`);let n=it(r,"baseUrl");lt(n)||(n="https://www.twse.com.tw"),n=n.replace(/\/+$/,"");let a=Ht(r,Ze),s=a.showLog,i=gt(e)?e.filter(Boolean):[],o=`${n}/rwd/zh/marginTrading/MI_MARGN?date=${t}&selectType=ALL&response=json`;s&&(console.log(`Fetching TWSE margin data: ${t}`),console.log(`Target: ${i.length>0?i.join(", "):"All Market"}`),console.log(`URL: ${o}`));let l=await Nt(o,a);if("OK"!==it(l,"stat"))throw new Error(`TWSE MI_MARGN API returned: ${it(l,"stat")}`);let u=Array.isArray(l.tables)?l.tables:[],c=u.find(t=>it(t,"data.length",0)>0&&it(t,"title","").includes("融資融券彙總"));if(c||(c=u.find(t=>it(t,"data.length",0)>0&&Array.isArray(t.fields)&&t.fields.some(t=>String(t).includes("融資"))&&t.fields.some(t=>String(t).includes("融券")))),!c||!gt(it(c,"data")))throw new Error("TWSE MI_MARGN: 找不到融資融券彙總資料表");if(!Array.isArray(c.fields)||c.fields.length<16)throw new Error(`TWSE 融資融券資料欄數不符預期(fields ${it(c,"fields.length","N/A")} 欄,應 >= 16),可能 API 格式變更`);let h=c.data;if(i.length>0&&(h=h.filter(t=>i.includes(String(t[0]||"").trim())),0===h.length))throw new Error(`指定個股 ${i.join(",")} 不在上市融資融券資料中(可能為上櫃股或代碼有誤)`);let f=h.map(t=>{let e=Be(t[2]),r=Be(t[3]),n=Be(t[6]),a=Be(t[5]),s=Be(t[8]),i=Be(t[9]),o=Be(t[12]),l=Be(t[11]);return{code:String(t[0]||"").trim(),name:String(t[1]||"").trim(),marginBuy:e,marginSell:r,marginCashRepay:Be(t[4]),marginPrevBalance:a,marginBalance:n,marginChange:n-a,marginLimit:Be(t[7]),shortSell:i,shortBuy:s,shortCashRepay:Be(t[10]),shortPrevBalance:l,shortBalance:o,shortChange:o-l,shortLimit:Be(t[13]),offset:Be(t[14]),note:String(t[15]||"").trim()}});return s&&console.log(`Fetched ${f.length} records.`),{source:"twse_margin",date:t,count:f.length,data:f}},fetchTpexMargin:async function(t,e,r={}){if(!lt(t)||!/^\d{8}$/.test(t))throw new Error(`日期參數無效:格式須為 YYYYMMDD (收到 "${t}")`);if(!Ue(t))throw new Error(`日期參數無效:不合法的日期 (${t})`);let n=it(r,"baseUrl");lt(n)||(n="https://www.tpex.org.tw"),n=n.replace(/\/+$/,"");let a=Ht(r,Ke),s=a.showLog,i=gt(e)?e.filter(Boolean):[],o=qe(t),l=`${n}/web/stock/margin_trading/margin_balance/margin_bal_result.php?l=zh-tw&d=${o}&o=json`;s&&(console.log(`Fetching TPEX margin data: ${t} (${o})`),console.log(`Target: ${i.length>0?i.join(", "):"All Market"}`),console.log(`URL: ${l}`));let u=await Nt(l,a),c=Array.isArray(u.tables)?u.tables:[],h=c.find(t=>it(t,"data.length",0)>0&&Array.isArray(t.fields)&&t.fields.some(t=>String(t).includes("資餘額"))&&t.fields.some(t=>String(t).includes("券餘額")))||c.find(t=>it(t,"data.length",0)>0);if(!h||!gt(it(h,"data")))throw new Error("TPEX margin API returned no data. Possibly a holiday or data not yet available.");let f=Array.isArray(h.fields)?h.fields:[];if(!String(f[6]||"").includes("資餘額")||!String(f[14]||"").includes("券餘額"))throw new Error(`TPEX 融資融券資料欄位佈局與預期不符(index6 應含「資餘額」實為「${f[6]??""}」、index14 應含「券餘額」實為「${f[14]??""}」);可能 API 格式變更,停止解析以免產生錯誤資料`);let g=h.data;if(i.length>0&&(g=g.filter(t=>i.includes(String(t[0]||"").trim())),0===g.length))throw new Error(`指定個股 ${i.join(",")} 不在上櫃融資融券資料中(可能為上市股或代碼有誤)`);let d=g.map(t=>{let e=Be(t[2]),r=Be(t[3]),n=Be(t[4]),a=Be(t[6]),s=Be(t[10]),i=Be(t[11]),o=Be(t[12]),l=Be(t[14]);return{code:String(t[0]||"").trim(),name:String(t[1]||"").trim(),marginBuy:r,marginSell:n,marginCashRepay:Be(t[5]),marginPrevBalance:e,marginBalance:a,marginChange:a-e,marginLimit:Be(t[9]),shortSell:i,shortBuy:o,shortCashRepay:Be(t[13]),shortPrevBalance:s,shortBalance:l,shortChange:l-s,shortLimit:Be(t[17]),offset:Be(t[18]),note:String(t[19]||"").trim()}});return s&&console.log(`Fetched ${d.length} records.`),{source:"tpex_margin",date:t,count:d.length,data:d}},fetchTwseT86:async function(t,e,r={}){if(!lt(t)||!/^\d{8}$/.test(t))throw new Error(`dateStr must be YYYYMMDD, got: ${t}`);if(!Ue(t))throw new Error(`dateStr 不是合法日期: ${t}`);let n=it(r,"baseUrl");lt(n)||(n="https://www.twse.com.tw"),n=n.replace(/\/+$/,"");let a=Ht(r,Ve),s=a.showLog,i=gt(e)?e:[],o=`${n}/rwd/zh/fund/T86?response=json&date=${t}&selectType=ALL`;s&&(console.log(`Fetching from: ${o}`),console.log(`Target: ${0===i.length?"All Market":i.join(", ")}`));let l=await Nt(o,a);if("OK"!==it(l,"stat"))throw new Error(`TWSE T86 API returned: ${it(l,"stat")}`);let u=it(l,"fields"),c=it(l,"data");if(!Array.isArray(c))throw new Error("TWSE T86: data not found in response.");if(!Array.isArray(u))throw new Error("TWSE T86: fields not found in response.");let h=c.map(t=>{let e={};return u.forEach((r,n)=>{let a=t[n];"string"==typeof a&&(a=a.trim()),e[r]=a}),e});if(i.length>0){let t=u.find(t=>String(t).includes("證券代號"));if(!t)throw new Error("無法篩選個股:API 回應中找不到證券代號欄位");h=h.filter(e=>i.includes(e[t]))}return s&&console.log(`Fetched ${h.length} records.`),{source:"twse",date:t,data:h}},fetchTpex3insti:async function(t,e,r={}){if(!lt(t)||!/^\d{8}$/.test(t))throw new Error(`dateStr must be YYYYMMDD, got: ${t}`);if(!Ue(t))throw new Error(`dateStr 不是合法日期: ${t}`);let n=it(r,"baseUrl");lt(n)||(n="https://www.tpex.org.tw"),n=n.replace(/\/+$/,"");let a=Ht(r,ze),s=a.showLog,i=gt(e)?e:[],o=`${n}/web/stock/3insti/daily_trade/3itrade_hedge_result.php?l=zh-tw&t=D&d=${qe(t)}&o=json`;s&&(console.log(`Fetching from: ${o}`),console.log(`Target: ${0===i.length?"All Market":i.join(", ")}`));let l=await Nt(o,a);if(!gt(it(l,"tables")))throw new Error("TPEX 3insti: tables not found in response. Possibly a holiday or no data.");let u=l.tables,c=u.find(t=>Array.isArray(t.fields)&&t.fields.some(t=>String(t).includes("代號"))&&t.fields.some(t=>String(t).includes("名稱")))||u[0],h=it(c,"fields"),f=it(c,"data");if(!Array.isArray(h)||!Array.isArray(f))throw new Error("TPEX 3insti: data/fields not found in table.");!function(t){if(t.length!==We.length)throw new Error(`TPEX 3insti: 欄數異常,預期 ${We.length} 得 ${t.length}(疑似 API 改版,請重新校準 TPEX_FIELD_MAP)`);if(!String(t[0]).includes("代號")||!String(t[1]).includes("名稱"))throw new Error(`TPEX 3insti: 前兩欄非「代號/名稱」(得「${t[0]}」「${t[1]}」,疑似 API 改版)`);if(!String(t[t.length-1]).includes("合計"))throw new Error(`TPEX 3insti: 末欄非「合計」(得「${t[t.length-1]}」,疑似 API 改版)`);let e=["買進股數","賣出股數","買賣超股數"];for(let r=2;r<t.length-1;r++){let n=e[(r-2)%3];if(String(t[r])!==n)throw new Error(`TPEX 3insti: 第 ${r} 欄預期「${n}」得「${t[r]}」(疑似 API 改版,欄序已變)`)}}(h);let g=f.map(t=>{let e={};return We.forEach((r,n)=>{let a=t[n];"string"==typeof a&&(a=a.trim()),e[r]=a}),e}).filter(t=>0===i.length||i.includes(t["代號"]));return s&&console.log(`Fetched ${g.length} records.`),{source:"tpex",date:t,data:g}},fetchTaifex:async function(t,e={}){if(!lt(t)||!/^\d{8}$/.test(t))throw new Error(`dateStr 須為 YYYYMMDD 字串,得到: ${t}`);if(!Ue(t))throw new Error(`dateStr 非合法日期: ${t}`);let r=it(e,"baseUrl");lt(r)||(r="https://www.taifex.com.tw"),r=r.replace(/\/+$/,"");let n=Ht(e,Re),a=n.showLog,s={baseUrl:r,showLog:a,optFetch:n},i=it(e,"interRequestDelayMs");i=Ct(i)?Lt(i):1e3;let o=`${t.substring(0,4)}/${t.substring(4,6)}/${t.substring(6,8)}`;a&&console.log(`Fetching TAIFEX data for ${t} (${o})`);let l=[],u=null,c=null,h=null;try{u=await async function(t,e){let r=`${e.baseUrl}/cht/3/futDataDown?down_type=1&queryStartDate=${t}&queryEndDate=${t}&commodity_id=TX`;e.showLog&&console.log(`Fetching futures data: ${r}`);let n=Fe(await Nt(r,{...e.optFetch,label:`${Re.label} Futures`}));if(0===n.length)throw new Error("台指期行情:無資料(可能非交易日)");let a=n.filter(t=>"TX"===t["契約"]&&!t["到期月份(週別)"].includes("/"));if(0===a.length)throw new Error("台指期行情:找不到 TX 合約資料");a.sort((t,e)=>t["到期月份(週別)"].trim().localeCompare(e["到期月份(週別)"].trim()));let s=a[0]["到期月份(週別)"].trim(),i=a.filter(t=>t["到期月份(週別)"].trim()===s),o=i.find(t=>"一般"===t["交易時段"]),l=i.find(t=>"盤後"===t["交易時段"]),u={contractMonth:s,open:null,high:null,low:null,close:null,settlement:null,volume:null,afterHoursClose:null,afterHoursSettlement:null,afterHoursVolume:null};return o&&(u.open=He(o["開盤價"]),u.high=He(o["最高價"]),u.low=He(o["最低價"]),u.close=He(o["收盤價"]),u.settlement=He(o["結算價"]),u.volume=He(o["成交量"])),l&&(u.afterHoursClose=He(l["收盤價"]),u.afterHoursSettlement=He(l["結算價"]),u.afterHoursVolume=He(l["成交量"])),e.showLog&&(console.log(` 台指期近月 (${s}): 開${u.open} 高${u.high} 低${u.low} 收${u.close} 結算${u.settlement} 量${u.volume}`),null!==u.afterHoursClose&&console.log(` 盤後: 收${u.afterHoursClose} 結算${u.afterHoursSettlement} 量${u.afterHoursVolume}`)),u}(o,s)}catch(t){let e=`台指期行情: ${it(t,"message",t)}`;a&&console.error(e),l.push(e)}await It(i);try{c=await async function(t,e){let r=`${e.baseUrl}/cht/3/futContractsDateDown?queryStartDate=${t}&queryEndDate=${t}&commodityId=TXF`;e.showLog&&console.log(`Fetching institutional data: ${r}`);let n=Fe(await Nt(r,{...e.optFetch,label:`${Re.label} Institutional`}));if(0===n.length)throw new Error("三大法人期貨未平倉:無資料(可能非交易日)");let a={};for(let t of n){let r=t["身份別"],n=Ye[r];n&&(a[n]={longContracts:He(t["多方未平倉口數"]),longAmount:He(t["多方未平倉契約金額(千元)"]),shortContracts:He(t["空方未平倉口數"]),shortAmount:He(t["空方未平倉契約金額(千元)"]),netContracts:He(t["多空未平倉口數淨額"]),netAmount:He(t["多空未平倉契約金額淨額(千元)"]),tradingLong:He(t["多方交易口數"]),tradingShort:He(t["空方交易口數"]),tradingNet:He(t["多空交易口數淨額"])},e.showLog&&console.log(` ${r}: 未平倉淨額 ${a[n].netContracts} 口`))}if(0===Object.keys(a).length)throw new Error("三大法人期貨未平倉:無法解析身份別(可能官方變更欄位用語)");return a}(o,s)}catch(t){let e=`三大法人: ${it(t,"message",t)}`;a&&console.error(e),l.push(e)}await It(i);try{h=await async function(t,e){let r=`${e.baseUrl}/cht/3/pcRatioDown?queryStartDate=${t}&queryEndDate=${t}`;e.showLog&&console.log(`Fetching P/C ratio: ${r}`);let n=Fe(await Nt(r,{...e.optFetch,label:`${Re.label} PCRatio`}));if(0===n.length)throw new Error("Put/Call Ratio:無資料(可能非交易日)");let a=n[0],s={putVolume:He(a["賣權成交量"]),callVolume:He(a["買權成交量"]),ratio:He(a["買賣權成交量比率%"]),putOpenInterest:He(a["賣權未平倉量"]),callOpenInterest:He(a["買權未平倉量"]),openInterestRatio:He(a["買賣權未平倉量比率%"])};return e.showLog&&console.log(` Put ${s.putVolume} / Call ${s.callVolume} = ${s.ratio}%`),s}(o,s)}catch(t){let e=`Put/Call Ratio: ${it(t,"message",t)}`;a&&console.error(e),l.push(e)}if(!(u||c||h)){let t=new Error(`所有資料抓取失敗: ${l.join("; ")}`);throw t.errors=l,t}return{date:t,futures:u?{tx:u}:null,institutional:c||null,pcRatio:h||null,errors:l}},fetchTwDataHoliday:async function(t,e={}){let r=it(e,"url");lt(r)||(r="https://openapi.twse.com.tw/v1/holidaySchedule/holidaySchedule");let n=Ht(e,Xe),a=await Nt(r,n),s=null;try{s=JSON.parse(a)}catch(t){throw new Error("API 回傳非 JSON 格式(可能為維護頁面或網路中介),請稍後再試")}if(!gt(s))throw new Error("API 回傳空陣列或格式異常");let i=s.filter(t=>!function(t){let e=String(it(t,"Name","")),r=String(it(t,"Description",""));return/交易日/.test(e)||/市場無交易/.test(e)||/開始交易/.test(r)||/最後交易/.test(r)}(t)).map(t=>{return{date:(e=it(t,"Date",""),`${parseInt(e.substring(0,e.length-4),10)+1911}${e.substring(e.length-4)}`),rocDate:it(t,"Date",""),name:it(t,"Name",""),weekday:it(t,"Weekday",""),description:String(it(t,"Description","")||"").replace(/<br\s*\/?>/gi,"").trim()};var e}),o=new Set,l=i.filter(t=>!o.has(t.date)&&(o.add(t.date),!0));l.sort((t,e)=>t.date.localeCompare(e.date));let u={dataYear:l.length>0?l[0].date.substring(0,4):null,totalHolidays:l.length,holidays:l};if(lt(t)&&/^\d{8}$/.test(t)){u.checkDate=t;let e=l.find(e=>e.date===t);u.isHoliday=!!e,u.holidayName=e?e.name:null}return u},fetchAisixiang:ge,fetchGuancha:{BASE_URL:pe,USER_AGENT:we,MAX_PAGES:50,KNOWN_TOPICS:ye,safeFilename:te,fetchAuthorsList:De,lookupAuthor:Me,lookupTopic:Ae,fetchAuthorArticles:async function(t={}){let e=$e(t),r=it(t,"name");lt(r)||(r="");let n=it(t,"slug");if(lt(n)||(n=""),!r&&!n)throw new Error("需要 name 或 slug");let a=n,s=r;if(!a){e.showLog&&process.stderr.write("[info] fetching authors index from / ...\n");let n=await De(t),i=Me(n,r);if(!i)return{status:"success",site:"guancha",mode:"author",query:r,fetched_at:(new Date).toISOString(),authors_count:n.length,count:0,items:[],message:`"${r}" 不在觀察者網作者索引中(共 ${n.length} 位)。尚無此作者文章。提醒:本函數不轉繁簡,呼叫端負責用站方登錄字形(簡體)。`};a=i.slug,s=i.name}let i=`${e.baseUrl}/${a}`,o=null;try{o=await ve(a,e)}catch(t){return{status:"error",site:"guancha",mode:"author",query:s||a,resolved:{slug:a,url:i,name:s},fetched_at:(new Date).toISOString(),reason:t.reason,error:t.message}}return{status:"success",site:"guancha",mode:"author",query:s||a,resolved:{slug:a,url:i,name:s,pages_fetched:o.pages_fetched},fetched_at:(new Date).toISOString(),count:o.items.length,items:o.items}},fetchKeywordArticles:async function(t,e={}){if(!lt(t))throw new Error("需要 keyword");return{status:"error",site:"guancha",mode:"keyword",query:t,fetched_at:(new Date).toISOString(),error:"unsupported-by-curl",message:"觀察者網搜尋 API(s.guancha.cn/main/search-v2)由 sojson.v4 混淆並走 MD5 簽名,本函數(純 curl 路徑)無法支援。如已知作者拼音 slug,請改用 fetchAuthorArticles({ slug });如已知主題 slug,請改用 fetchTopicArticles({ slug })。"}},fetchTitleArticles:async function(t,e={}){if(!lt(t))throw new Error("需要 keyword");return{status:"error",site:"guancha",mode:"title",query:t,fetched_at:(new Date).toISOString(),error:"unsupported-by-curl",message:"觀察者網無分「標題」與「全文」搜尋(兩者同源),搜尋 API 由 sojson.v4 混淆並走 MD5 簽名,本函數(純 curl 路徑)無法支援。如已知作者拼音 slug,請改用 fetchAuthorArticles({ slug })。"}},fetchTopicArticles:async function(t={}){let e=$e(t),r=it(t,"name");lt(r)||(r="");let n=it(t,"slug");if(lt(n)||(n=""),!r&&!n)throw new Error("需要 name 或 slug");let a=n,s=r;if(!a){let t=Ae(r);if(!t)return{status:"error",site:"guancha",mode:"topic",query:r,fetched_at:(new Date).toISOString(),topics_count:ye.length,error:"topic-not-in-table",message:`主題 "${r}" 不在已知主題對照表中(共 ${ye.length} 個)。觀察者網無公開主題清單頁,本函數採手工對照。建議直接傳 slug(拼音 slug)。本函數不自動轉向,請呼叫端決定是否重試。`};a=t.slug,s=t.name}let i=`${e.baseUrl}/${a}`,o=null;try{o=await ve(a,e)}catch(t){return{status:"error",site:"guancha",mode:"topic",query:s||a,resolved:{slug:a,url:i,name:s},fetched_at:(new Date).toISOString(),reason:t.reason,error:t.message}}return{status:"success",site:"guancha",mode:"topic",query:s||a,resolved:{slug:a,url:i,name:s,pages_fetched:o.pages_fetched},fetched_at:(new Date).toISOString(),count:o.items.length,items:o.items}},fetchArticle:async function(t={}){let e=$e(t),r=it(t,"url");if(!lt(r))throw new Error("需要 url");let n=null;try{n=await be(r,e)}catch(t){return{status:"error",site:"guancha",mode:"fetch",url:r,fetched_at:(new Date).toISOString(),error:t.message||"fetch failed"}}let a=function(t){let e=t.match(/<title>([\s\S]*?)<\/title>/i);return e?Jt(e[1].trim()):null}(n)||"",s=Qt(n,"content all-txt");if("观察者网"===a||"觀察者網"===a||!s)return{status:"error",site:"guancha",mode:"fetch",url:r,fetched_at:(new Date).toISOString(),error:"article-not-found",message:`無此文章(title="${a}",可能已下架或 URL 不正確)。`};let i=a.replace(/[_\-\s||]*(?:观察者网|觀察者網)\s*$/u,"").trim(),o=function(t){let e=t.match(/【([^】]{4,120})】/);return e?e[1].replace(/<[^>]+>/g,"").trim():null}(s)||"",l=function(t){let e=t.match(/<span[^>]*>(20\d{2}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})<\/span>/);return e?e[1]:null}(n)||"",u=(new Date).toISOString().slice(0,10),c=Vt(s,{image:!0}),h=t=>String(t).replace(/"/g,'\\"'),f=`---\ntitle: "${h(i)}"\nsource: "${r}"\nauthor: "${h(o)}"\npublished: "${l}"\ncreated: ${u}\ndescription:\n---\n${c}\n`;return{status:"success",site:"guancha",mode:"fetch",url:r,title:i,author:o,published:l,chars:c.length,markdown:f}}}};return Qe});
|
|
7
|
+
//# sourceMappingURL=w-dwdata-hub.umd.js.map
|