linke-sdufe 0.2.0 → 0.3.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/package.json +1 -1
- package/src/adapter.js +61 -12
- package/src/parsers.js +19 -6
package/package.json
CHANGED
package/src/adapter.js
CHANGED
|
@@ -45,10 +45,52 @@ const REQUEST_TIMEOUT_MS = 20000
|
|
|
45
45
|
* 翻页:listPath=栏目首页,page N → pagePath/{N-1}.htm。
|
|
46
46
|
* 学工部通知栏目实测=info/1093(数据源地图旧记 1090 实测 404)。
|
|
47
47
|
*/
|
|
48
|
+
/**
|
|
49
|
+
* 校园官网公开通知源注册表(T46 起 36 源;数据源地图 v4 全站探测 +
|
|
50
|
+
* 2026-09-04 执行者逐站实勘:列表/栏目/翻页/详情容器逐站 curl 实证)。
|
|
51
|
+
* 翻页:listPath 去掉 .htm 即 pagerPath(page N → {pagerPath}/{N-1}.htm);
|
|
52
|
+
* pagePath 为空=该源翻页形态特殊未实证(如 jinrong),page>1 明确报错。
|
|
53
|
+
* infoPaths:列表条目栏目过滤(防导航混入);详情白名单按 origin(不限栏目)。
|
|
54
|
+
* 不接:zgjjyjy(无通知栏目)、xcb(内容发新闻网)、tuanwei/grads 等
|
|
55
|
+
* 防护壳与登录系统(数据源地图标注排除)。
|
|
56
|
+
*/
|
|
48
57
|
export const PORTAL_NOTICE_SOURCES = {
|
|
49
|
-
jwc: { name: '教务处官网', origin: 'https://jwc.sdufe.edu.cn', listPath: '/zxdt/tzgg.htm', pagePath: '/zxdt/tzgg',
|
|
50
|
-
main: { name: '学校主站', origin: 'https://www.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg',
|
|
51
|
-
xgb: { name: '学工部官网', origin: 'https://student.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg',
|
|
58
|
+
jwc: { name: '教务处官网', origin: 'https://jwc.sdufe.edu.cn', listPath: '/zxdt/tzgg.htm', pagePath: '/zxdt/tzgg', infoPaths: ['1043'] },
|
|
59
|
+
main: { name: '学校主站', origin: 'https://www.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1068'] },
|
|
60
|
+
xgb: { name: '学工部官网', origin: 'https://student.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1093'] },
|
|
61
|
+
archives: { name: '档案馆', origin: 'https://archives.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1053'] },
|
|
62
|
+
bcf: { name: '区块链金融实验室', origin: 'https://bcf.sdufe.edu.cn', listPath: '/xwzx/zhxw.htm', pagePath: '/xwzx/zhxw', infoPaths: ['1072'] },
|
|
63
|
+
cs: { name: '计算机学院', origin: 'https://cs.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1014'] },
|
|
64
|
+
gkgc: { name: '管科学院', origin: 'https://gkgc.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '/tzgg', infoPaths: ['1099'] },
|
|
65
|
+
gonghui: { name: '工会', origin: 'https://gonghui.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1061'] },
|
|
66
|
+
gsglxy: { name: '工商管理学院', origin: 'https://gsglxy.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1193'] },
|
|
67
|
+
hf: { name: '合作发展', origin: 'https://hf.sdufe.edu.cn', listPath: '/fxljy1/tzgg.htm', pagePath: '/fxljy1/tzgg', infoPaths: ['1194'] },
|
|
68
|
+
insurance: { name: '保险学院', origin: 'https://insurance.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '/tzgg', infoPaths: ['1059', '1152'] },
|
|
69
|
+
jinrong: { name: '金融学院', origin: 'https://jinrong.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '', infoPaths: ['1043', '1160'] },
|
|
70
|
+
jiwei: { name: '纪委', origin: 'https://jiwei.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '/tzgg', infoPaths: ['1080'] },
|
|
71
|
+
jrfzyjy: { name: '金融发展研究院', origin: 'https://jrfzyjy.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1013'] },
|
|
72
|
+
jsgzb: { name: '教师工作部', origin: 'https://jsgzb.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1127'] },
|
|
73
|
+
kyc: { name: '科研处', origin: 'https://kyc.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '/tzgg', infoPaths: ['1077'] },
|
|
74
|
+
law: { name: '法学院', origin: 'https://law.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1287'] },
|
|
75
|
+
lib: { name: '图书馆', origin: 'https://lib.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1094'] },
|
|
76
|
+
mba: { name: 'MBA学院', origin: 'https://mba.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '/tzgg', infoPaths: ['1372', '1376'] },
|
|
77
|
+
mpa: { name: 'MPA中心', origin: 'https://mpa.sdufe.edu.cn', listPath: '/xwtz/tzgg.htm', pagePath: '/xwtz/tzgg', infoPaths: ['1027'] },
|
|
78
|
+
news: { name: '新闻网', origin: 'https://news.sdufe.edu.cn', listPath: '/cdyw.htm', pagePath: '/cdyw', infoPaths: ['1071'] },
|
|
79
|
+
rcb: { name: '人才办', origin: 'https://rcb.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1023'] },
|
|
80
|
+
rsc: { name: '人事处', origin: 'https://rsc.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1047'] },
|
|
81
|
+
sam: { name: '统数学院', origin: 'https://sam.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '/tzgg', infoPaths: ['1012', '1051'] },
|
|
82
|
+
sce: { name: '继续教育学院', origin: 'https://sce.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1124', '1125', '1164', '1282', '2241'] },
|
|
83
|
+
sie: { name: '国际教育学院', origin: 'https://sie.sdufe.edu.cn', listPath: '/index/tzgg2.htm', pagePath: '/index/tzgg2', infoPaths: ['1063'] },
|
|
84
|
+
site: { name: '国贸学院', origin: 'https://site.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1009', '1063', '1064'] },
|
|
85
|
+
spft: { name: '财税学院', origin: 'https://spft.sdufe.edu.cn', listPath: '/tzgg1.htm', pagePath: '/tzgg1', infoPaths: ['1010'] },
|
|
86
|
+
spm: { name: '公共管理学院', origin: 'https://spm.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1324'] },
|
|
87
|
+
tzb: { name: '统战部', origin: 'https://tzb.sdufe.edu.cn', listPath: '/tzgg.htm', pagePath: '/tzgg', infoPaths: ['1031'] },
|
|
88
|
+
xkjsbgs: { name: '学科办', origin: 'https://xkjsbgs.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1106'] },
|
|
89
|
+
xxgk: { name: '信息公开', origin: 'https://xxgk.sdufe.edu.cn', listPath: '/ejlm/xwyxk/xwsybfhsyjd.htm', pagePath: '/ejlm/xwyxk/xwsybfhsyjd', infoPaths: ['1115'] },
|
|
90
|
+
yjsy: { name: '研究生院', origin: 'https://yjsy.sdufe.edu.cn', listPath: '/index/tzgg1.htm', pagePath: '/index/tzgg1', infoPaths: ['1027', '1030', '1031', '1032', '1043', '1058'] },
|
|
91
|
+
ys: { name: '燕山学院', origin: 'https://ys.sdufe.edu.cn', listPath: '/xwtz/zhxw.htm', pagePath: '/xwtz/zhxw', infoPaths: ['1018', '1053'] },
|
|
92
|
+
zfjxpj: { name: '政府绩效评价', origin: 'https://zfjxpj.sdufe.edu.cn', listPath: '/zxdt/tzgg.htm', pagePath: '/zxdt/tzgg', infoPaths: ['1010', '1015', '1018', '1037'] },
|
|
93
|
+
zzb: { name: '组织部', origin: 'https://zzb.sdufe.edu.cn', listPath: '/index/tzgg.htm', pagePath: '/index/tzgg', infoPaths: ['1046', '1076'] },
|
|
52
94
|
}
|
|
53
95
|
|
|
54
96
|
/** 课程属性中文名 → zzdKcSX 表单码(强智 kbxx_kc_ifr 口径) */
|
|
@@ -407,17 +449,24 @@ export function createSdufeAdapter(env) {
|
|
|
407
449
|
},
|
|
408
450
|
|
|
409
451
|
/**
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
452
|
+
* 校园官网公开通知源列表(T46 三源起步,T46 扩展 36 源;博达 CMS 同构
|
|
453
|
+
* 一套解析,逐站实勘配置见 PORTAL_NOTICE_SOURCES)。
|
|
454
|
+
* 翻页口径:page 1=listPath,page N={pagePath}/{N-1}.htm;
|
|
455
|
+
* pagePath 为空=该源翻页未实证(page>1 明确报错,不静默回首页)。
|
|
413
456
|
* 礼貌纪律:只抓请求的页,不做归档批量预取。
|
|
414
457
|
*/
|
|
415
458
|
async fetchPortalNotices(site, page = 1) {
|
|
416
459
|
const src = PORTAL_NOTICE_SOURCES[site]
|
|
417
460
|
if (!src) {
|
|
418
|
-
throw new LinkeError('BAD_SOURCE', `未知通知源:${site}
|
|
461
|
+
throw new LinkeError('BAD_SOURCE', `未知通知源:${site}`, {
|
|
462
|
+
exitCode: EXIT.GENERAL,
|
|
463
|
+
hint: 'source 取源注册表 key(jwc=教务处 / main=主站 / xgb=学工部 / lib=图书馆 / yjsy=研究生院 / news=新闻网 / 各学院与职能部门拼音缩写)',
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
if (page > 1 && !src.pagePath) {
|
|
467
|
+
throw new LinkeError('NO_PAGER', `${src.name}暂不支持翻页(该站翻页形态特殊未实证)`, {
|
|
419
468
|
exitCode: EXIT.GENERAL,
|
|
420
|
-
hint: '
|
|
469
|
+
hint: '本源仅可查第一页(page=1)',
|
|
421
470
|
})
|
|
422
471
|
}
|
|
423
472
|
const path = page <= 1 ? src.listPath : `${src.pagePath}/${page - 1}.htm`
|
|
@@ -437,11 +486,11 @@ export function createSdufeAdapter(env) {
|
|
|
437
486
|
// 栏目过滤:列表页含导航/侧栏的 info 链接(如主站「学校概况」),
|
|
438
487
|
// 只保留本源通知栏目条目
|
|
439
488
|
return parsePortalNotices(await response.text(), src.origin)
|
|
440
|
-
.filter((x) => !src.
|
|
489
|
+
.filter((x) => !src.infoPaths || !src.infoPaths.length || src.infoPaths.some((c) => x.url.includes(`/info/${c}/`)))
|
|
441
490
|
},
|
|
442
491
|
|
|
443
492
|
/**
|
|
444
|
-
* 校园官网公开通知详情全文(T46)。url
|
|
493
|
+
* 校园官网公开通知详情全文(T46)。url 限源注册表内域名(白名单防抓任意站);
|
|
445
494
|
* 礼貌纪律:只抓用户点名的这一篇,不做批量预取。
|
|
446
495
|
*/
|
|
447
496
|
async fetchPortalNoticeDetail(url) {
|
|
@@ -456,9 +505,9 @@ export function createSdufeAdapter(env) {
|
|
|
456
505
|
}
|
|
457
506
|
const src = Object.values(PORTAL_NOTICE_SOURCES).find((s) => s.origin === parsed.origin)
|
|
458
507
|
if (!src || !/^\/info\/\d+\/\d+\.htm$/.test(parsed.pathname)) {
|
|
459
|
-
throw new LinkeError('BAD_URL', `URL
|
|
508
|
+
throw new LinkeError('BAD_URL', `URL 不在校内通知源范围:${url}`, {
|
|
460
509
|
exitCode: EXIT.GENERAL,
|
|
461
|
-
hint:
|
|
510
|
+
hint: `仅支持通知源注册表内各站(jwc/main/xgb/lib/yjsy/news/各学院等)的 info/{栏目}/{id}.htm 详情页`,
|
|
462
511
|
})
|
|
463
512
|
}
|
|
464
513
|
let response
|
package/src/parsers.js
CHANGED
|
@@ -876,25 +876,38 @@ function htmlToText(html) {
|
|
|
876
876
|
export function parsePortalNotices(html, baseUrl = '') {
|
|
877
877
|
const list = []
|
|
878
878
|
const seen = new Set()
|
|
879
|
-
|
|
879
|
+
// 兼容条目形态(逐站实勘判例):
|
|
880
|
+
// - href 属性位不定(news/xxgk:<a target="_blank" href="info/…">)
|
|
881
|
+
// - href 三种相对形态:../info/{栏}/{id}.htm、/info/…、info/…(无前导斜杠)
|
|
882
|
+
// - a 内可嵌日期 span(lib/yjsy/news/cs 等新模板:<a>标题<span class="spans">日期</span></a>)
|
|
883
|
+
const linkRe = /<a\s[^>]*?href="((?:[^"]*?\/)?info\/\d+\/\d+\.htm)"[^>]*>([\s\S]*?)<\/a>/g
|
|
880
884
|
let m
|
|
881
885
|
while ((m = linkRe.exec(html))) {
|
|
882
886
|
let url = m[1]
|
|
883
887
|
if (!/^https?:/i.test(url)) {
|
|
884
|
-
//
|
|
885
|
-
url = baseUrl.replace(/\/$/, '') + '/' + url.replace(/^(\.\.\/)+/, '')
|
|
888
|
+
// 相对列表页目录 → 站点根 /info/x(博达栏目均为根级 info/)
|
|
889
|
+
url = baseUrl.replace(/\/$/, '') + '/' + url.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
886
890
|
}
|
|
887
891
|
if (seen.has(url)) continue
|
|
888
892
|
seen.add(url)
|
|
893
|
+
const inner = decodeEntities(m[2].replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim())
|
|
894
|
+
// yjsy/cs 等新模板把日期放 a 内文本开头 → 剥前缀日期当 date
|
|
895
|
+
let title = inner
|
|
896
|
+
let date = ''
|
|
897
|
+
const inline = inner.match(/^(\d{4}-\d{2}-\d{2})\s*(.*)$/)
|
|
898
|
+
const inlineCn = inner.match(/^(\d{4}年\d{1,2}月\d{1,2}日)\s*(.*)$/)
|
|
899
|
+
if (inline) { date = inline[1]; title = inline[2] || inner }
|
|
900
|
+
else if (inlineCn) { date = inlineCn[1]; title = inlineCn[2] || inner }
|
|
889
901
|
const after = html.slice(m.index + m[0].length, m.index + m[0].length + 600)
|
|
890
902
|
const before = html.slice(Math.max(0, m.index - 600), m.index)
|
|
891
|
-
|
|
903
|
+
// 日期其余形态:a 内 span 徽章 → a 后窗口 → a 前 day+month 分元素(学工部)
|
|
904
|
+
if (!date) date = (m[2].match(/\d{4}-\d{2}-\d{2}/) || [])[0] || ''
|
|
905
|
+
if (!date) date = (after.match(/\d{4}-\d{2}-\d{2}/) || [])[0] || ''
|
|
892
906
|
if (!date) {
|
|
893
|
-
// 学工部形态:<p class="day">13</p><p class="month">2026-07</p>
|
|
894
907
|
const dm = before.match(/class="day">\s*(\d{1,2})\s*<\/p>\s*<p class="month">\s*(\d{4}-\d{2})/)
|
|
895
908
|
if (dm) date = `${dm[2]}-${dm[1].padStart(2, '0')}`
|
|
896
909
|
}
|
|
897
|
-
list.push({ title
|
|
910
|
+
list.push({ title, url, date })
|
|
898
911
|
}
|
|
899
912
|
return list
|
|
900
913
|
}
|