yaohao 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/LICENSE +21 -0
- package/README.md +163 -0
- package/bin/yaohao.js +47 -0
- package/package.json +53 -0
- package/skills/yaohao/SKILL.md +128 -0
- package/src/commands/calendar.js +29 -0
- package/src/commands/cron.js +87 -0
- package/src/commands/eligibility.js +28 -0
- package/src/commands/family.js +17 -0
- package/src/commands/history.js +17 -0
- package/src/commands/init.js +102 -0
- package/src/commands/market.js +40 -0
- package/src/commands/notify.js +92 -0
- package/src/commands/result.js +18 -0
- package/src/commands/set.js +53 -0
- package/src/commands/status.js +17 -0
- package/src/commands/waitlist.js +17 -0
- package/src/commands/watch.js +159 -0
- package/src/constants.js +18 -0
- package/src/lib/config-manager.js +67 -0
- package/src/lib/notifier.js +190 -0
- package/src/output.js +15 -0
- package/src/source/_shared/crawl.js +169 -0
- package/src/source/_shared/parseUtils.js +141 -0
- package/src/source/_shared/titleClassify.js +30 -0
- package/src/source/beijing/calendar.js +65 -0
- package/src/source/beijing/constants.js +8 -0
- package/src/source/beijing/crawl.js +156 -0
- package/src/source/beijing/eligibility.js +110 -0
- package/src/source/beijing/index.js +23 -0
- package/src/source/beijing/parse.js +206 -0
- package/src/source/beijing/pdfExtract.js +41 -0
- package/src/source/beijing/service.js +190 -0
- package/src/source/guangzhou/calendar.js +54 -0
- package/src/source/guangzhou/constants.js +16 -0
- package/src/source/guangzhou/eligibility.js +88 -0
- package/src/source/guangzhou/index.js +22 -0
- package/src/source/guangzhou/parse.js +61 -0
- package/src/source/guangzhou/service.js +126 -0
- package/src/source/hangzhou/calendar.js +60 -0
- package/src/source/hangzhou/constants.js +16 -0
- package/src/source/hangzhou/eligibility.js +102 -0
- package/src/source/hangzhou/index.js +20 -0
- package/src/source/hangzhou/parse.js +59 -0
- package/src/source/hangzhou/service.js +122 -0
- package/src/source/index.js +54 -0
- package/src/source/shenzhen/calendar.js +44 -0
- package/src/source/shenzhen/constants.js +14 -0
- package/src/source/shenzhen/eligibility.js +90 -0
- package/src/source/shenzhen/index.js +20 -0
- package/src/source/shenzhen/parse.js +58 -0
- package/src/source/shenzhen/service.js +122 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { fetchHtml } from '../_shared/crawl.js';
|
|
2
|
+
import { parseListPage, parseDetailPage } from './parse.js';
|
|
3
|
+
import { URLS, SYSTEM_URL } from './constants.js';
|
|
4
|
+
|
|
5
|
+
const MAX_CACHE_AGE = 5 * 60 * 1000;
|
|
6
|
+
|
|
7
|
+
async function fetchList(opts = {}) {
|
|
8
|
+
const useCache = opts.cache !== false;
|
|
9
|
+
const page = await fetchHtml(URLS.gblList, { useCache, maxCacheAgeMs: MAX_CACHE_AGE });
|
|
10
|
+
return parseListPage(page.html, URLS.gblList);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function extractMarketMetrics(opts = {}) {
|
|
14
|
+
try {
|
|
15
|
+
const useCache = opts.cache !== false;
|
|
16
|
+
const items = await fetchList(opts);
|
|
17
|
+
|
|
18
|
+
const cfg = items.find((i) => i.kind === 'config_notice' && /配置数量/.test(i.title));
|
|
19
|
+
const lotteryNotice = items.find((i) => i.kind === 'lottery_notice');
|
|
20
|
+
const result = items.find((i) => i.kind === 'result');
|
|
21
|
+
|
|
22
|
+
const detailsToParse = [cfg, lotteryNotice, result].filter(Boolean);
|
|
23
|
+
const detailMetrics = {};
|
|
24
|
+
for (const it of detailsToParse) {
|
|
25
|
+
try {
|
|
26
|
+
const page = await fetchHtml(it.url, { useCache });
|
|
27
|
+
const d = parseDetailPage(page.html, it.url);
|
|
28
|
+
detailMetrics[it.kind] = { ...d.metrics, title: d.title, url: it.url, date: it.date };
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const cfgM = detailMetrics.config_notice || {};
|
|
33
|
+
const ln = detailMetrics.lottery_notice || {};
|
|
34
|
+
const totalAlloc = cfgM.totalAlloc ?? ln.totalAlloc ?? null;
|
|
35
|
+
const personalAlloc = cfgM.personalAlloc ?? ln.personalAlloc ?? null;
|
|
36
|
+
const unitAlloc = cfgM.unitAllocNum ?? ln.unitAllocNum ?? null;
|
|
37
|
+
const lotteryAlloc = cfgM.lotteryAlloc ?? ln.lotteryAlloc ?? null;
|
|
38
|
+
const biddingAlloc = cfgM.biddingAlloc ?? ln.biddingAlloc ?? null;
|
|
39
|
+
|
|
40
|
+
const data = {
|
|
41
|
+
city: 'shenzhen',
|
|
42
|
+
period: cfg?.period?.label || lotteryNotice?.period?.label || result?.period?.label || null,
|
|
43
|
+
source: SYSTEM_URL,
|
|
44
|
+
latestConfig: cfg ? { date: cfg.date, title: cfg.title, url: cfg.url } : null,
|
|
45
|
+
latestLottery: lotteryNotice ? { date: lotteryNotice.date, title: lotteryNotice.title, url: lotteryNotice.url } : null,
|
|
46
|
+
latestResult: result ? { date: result.date, title: result.title, url: result.url } : null,
|
|
47
|
+
alloc: { total: totalAlloc, personal: personalAlloc, unit: unitAlloc, lottery: lotteryAlloc, bidding: biddingAlloc },
|
|
48
|
+
details: detailMetrics,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const lines = [];
|
|
52
|
+
lines.push(`深圳小汽车摇号 ${data.period || '当期'} 形势播报`);
|
|
53
|
+
lines.push(`数据来源: ${SYSTEM_URL}`);
|
|
54
|
+
lines.push('');
|
|
55
|
+
if (cfg) {
|
|
56
|
+
lines.push(`【最新配置数量公告】${cfg.date}`);
|
|
57
|
+
lines.push(` ${cfg.title}`);
|
|
58
|
+
lines.push(` ${cfg.url}`);
|
|
59
|
+
}
|
|
60
|
+
if (lotteryNotice) {
|
|
61
|
+
lines.push('');
|
|
62
|
+
lines.push(`【最新摇号公告】${lotteryNotice.date}`);
|
|
63
|
+
lines.push(` ${lotteryNotice.title}`);
|
|
64
|
+
lines.push(` ${lotteryNotice.url}`);
|
|
65
|
+
}
|
|
66
|
+
if (result) {
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push(`【最新摇号结果公告】${result.date}`);
|
|
69
|
+
lines.push(` ${result.title}`);
|
|
70
|
+
lines.push(` ${result.url}`);
|
|
71
|
+
}
|
|
72
|
+
if (totalAlloc || personalAlloc || unitAlloc || lotteryAlloc || biddingAlloc) {
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push('【本期配置指标】');
|
|
75
|
+
if (totalAlloc) lines.push(` 总配置: ${fmt(totalAlloc)}`);
|
|
76
|
+
if (personalAlloc) lines.push(` 个人: ${fmt(personalAlloc)}`);
|
|
77
|
+
if (unitAlloc) lines.push(` 单位: ${fmt(unitAlloc)}`);
|
|
78
|
+
if (lotteryAlloc) lines.push(` 其中摇号: ${fmt(lotteryAlloc)}`);
|
|
79
|
+
if (biddingAlloc) lines.push(` 其中竞价: ${fmt(biddingAlloc)}`);
|
|
80
|
+
}
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push('提示:2026 年 4-12 月阶梯摇号,每 24 次未中签升 1 阶梯');
|
|
83
|
+
lines.push('完整中签率/具体编码需查询单篇公告全文');
|
|
84
|
+
return { ok: true, data, lines };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: `深圳数据抓取失败: ${err.message}`,
|
|
89
|
+
lines: [`错误: ${err.message}`, '', `请直接访问 ${SYSTEM_URL}`],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const watchTargets = {
|
|
95
|
+
result: {
|
|
96
|
+
desc: '深圳摇号结果',
|
|
97
|
+
async fetch(opts = {}) {
|
|
98
|
+
const items = await fetchList(opts);
|
|
99
|
+
return items.filter((i) => i.kind === 'result' || i.kind === 'config_notice' || i.kind === 'lottery_notice');
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
policy: {
|
|
103
|
+
desc: '深圳政策变化',
|
|
104
|
+
async fetch(opts = {}) {
|
|
105
|
+
const items = await fetchList(opts);
|
|
106
|
+
return items.filter((i) => i.kind === 'quota' || i.kind === 'faq' || i.kind === 'penalty');
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
window: {
|
|
110
|
+
desc: '深圳申请窗口/资格审核',
|
|
111
|
+
async fetch(opts = {}) {
|
|
112
|
+
const items = await fetchList(opts);
|
|
113
|
+
return items.filter((i) => i.kind === 'qualify_review' || i.kind === 'lottery_notice');
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function fmt(n) {
|
|
119
|
+
if (n == null) return '未知';
|
|
120
|
+
if (typeof n === 'number') return n.toLocaleString('zh-CN');
|
|
121
|
+
return String(n);
|
|
122
|
+
}
|