koishi-plugin-bns-rate 1.2.5 → 1.2.7
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/README.md +6 -5
- package/index.js +50 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| --- | --- |
|
|
11
11
|
| `bnsrate` / `神石` / `shenshi` | 查询当前时段最低神石比例 |
|
|
12
12
|
| `bnsrate -f` | 忽略缓存,强制刷新 |
|
|
13
|
-
| `bnsrate.box` | **报价区排版对照**:一条消息里给出 code / quote / list / none
|
|
13
|
+
| `bnsrate.box` | **报价区排版对照**:一条消息里给出 code / table / quote / list / none 五种样式,在手机上挑一个填进 `ratesBox` |
|
|
14
14
|
| `bnsrate.preview` | 只输出卡片 Markdown 源码(不发送卡片),调试用 |
|
|
15
15
|
|
|
16
16
|
## 卡片效果
|
|
@@ -36,18 +36,19 @@
|
|
|
36
36
|
|
|
37
37
|
报价区默认用**代码块**包起来,在客户端渲染成**灰色框**(`ratesBox: code`)。代码块是等宽字体,插件会用空格把两列**对齐**(数字位数不同也对齐)。
|
|
38
38
|
|
|
39
|
-
###
|
|
39
|
+
### 报价区排版可选五种(`ratesBox`)
|
|
40
40
|
|
|
41
41
|
| 值 | 效果 | QQ 官方文档支持 |
|
|
42
42
|
| --- | --- | --- |
|
|
43
43
|
| **`code`(默认)** | 代码块 → 均匀灰底圆角框、等宽字体、两列对齐 | ❌ 不在[支持清单](https://bot.qq.com/wiki/develop/api-v2/server-inter/message/type/markdown.html)(客户端能力,安卓新版本 / iOS 才有) |
|
|
44
|
+
| `table` | GFM 管道表格 → 带表头的表格 | ❌ 不在清单(客户端能力,实测部分客户端可渲染) |
|
|
44
45
|
| `quote` | 块引用 → 灰底 + 左侧竖条 | ✅ 明确支持 |
|
|
45
46
|
| `list` | 无序列表 → 无底色,缩进 + 圆点 | ✅ 明确支持 |
|
|
46
47
|
| `none` | 不包裹,纯文本行 | — |
|
|
47
48
|
|
|
48
|
-
拿不准就在群里发 **`bnsrate.box
|
|
49
|
+
拿不准就在群里发 **`bnsrate.box`**:它会发一条含全部五种样式的对照卡片,挑一个填进设置页即可。若手机上 ` ``` ` 或表格显示成了原文(一堆 `|` 竖线),说明该客户端不支持,换成官方支持的 `quote` / `list` 最稳。
|
|
49
50
|
|
|
50
|
-
> ⚠️ **本插件不输出任何 HTML 标签**。手机端 QQ(安卓 / iOS)不渲染 HTML,`<font color="#E60012">151.51</font>` 会被**原样显示成一串乱码文字**(实测确认)。因此 1.2.5
|
|
51
|
+
> ⚠️ **本插件不输出任何 HTML 标签**。手机端 QQ(安卓 / iOS)不渲染 HTML,`<font color="#E60012">151.51</font>` 会被**原样显示成一串乱码文字**(实测确认)。因此 1.2.5 起**移除了颜色功能**,报价一律按纯文本输出,五种排版在手机上都是干净的。
|
|
51
52
|
|
|
52
53
|
其它平台(OneBot / Telegram 等)、QQ 频道(`qqguild`)、以及卡片发送失败时,都会**自动回退为纯文本**,不会发不出消息。
|
|
53
54
|
|
|
@@ -90,7 +91,7 @@
|
|
|
90
91
|
| `button` | `true` | 是否显示底部跳转按钮 |
|
|
91
92
|
| `buttonText` | `点此直达DD373网站` | 按钮文字 |
|
|
92
93
|
| `buttonUrl` | `https://www.dd373.com/s-d5gqt8.html` | 按钮跳转链接 |
|
|
93
|
-
| `ratesBox` | `code` | 报价区排版:`code`
|
|
94
|
+
| `ratesBox` | `code` | 报价区排版:`code` 代码块/`table` 表格/`quote` 引用/`list` 列表/`none` 不包裹。发 `bnsrate.box` 可看五种对照 |
|
|
94
95
|
| `decimalsForward` | `2` | 「1元=xx神石」保留小数位(**截断**,不四舍五入),`0` = 原始值 |
|
|
95
96
|
| `decimalsReverse` | `4` | 「1神石=xx元」保留小数位(截断),`0` = 原始值 |
|
|
96
97
|
|
package/index.js
CHANGED
|
@@ -6,6 +6,17 @@ const DEFAULT_BUTTON_URL = 'https://www.dd373.com/s-d5gqt8.html'
|
|
|
6
6
|
const FORWARD_RE = /1元\s*=\s*(\d+\.?\d*)神石/g
|
|
7
7
|
const REVERSE_RE = /(?<!\d)1神石\s*=\s*(\d+\.\d+)元/
|
|
8
8
|
|
|
9
|
+
// 报价区排版方式(设置页下拉 + bnsrate.box 对照图都用这一份定义,避免两头不同步)
|
|
10
|
+
// ⚠️ 除 code/table 外都会输出 <font> 等 HTML 标签,而手机端不渲染 HTML,所以一律不加标签
|
|
11
|
+
const RATE_BOXES = ['code', 'table', 'quote', 'list', 'none']
|
|
12
|
+
const RATE_BOX_LABELS = {
|
|
13
|
+
code: 'A. code —— 代码块(灰色框 / 等宽 / 两列对齐)',
|
|
14
|
+
table: 'B. table —— 表格(带表头 / 需客户端支持)',
|
|
15
|
+
quote: 'C. quote —— 块引用(灰底 + 左侧竖条)',
|
|
16
|
+
list: 'D. list —— 无序列表(缩进 + 圆点)',
|
|
17
|
+
none: 'E. none —— 不包裹(纯文本行)',
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
// ===== 配置 Schema =====
|
|
10
21
|
|
|
11
22
|
const Config = Schema.object({
|
|
@@ -66,9 +77,9 @@ const Config = Schema.object({
|
|
|
66
77
|
.default(DEFAULT_BUTTON_URL)
|
|
67
78
|
.description('按钮跳转的链接'),
|
|
68
79
|
|
|
69
|
-
ratesBox: Schema.union(
|
|
80
|
+
ratesBox: Schema.union(RATE_BOXES)
|
|
70
81
|
.default('code')
|
|
71
|
-
.description('📦
|
|
82
|
+
.description('📦 报价区排版,拿不准就在群里发 bnsrate.box 看对照图再挑:<br>code = 代码块(灰色框、等宽、两列对齐)<br>table = 表格(带表头,不在 QQ 官方支持清单里,客户端能渲染才行)<br>quote = 块引用(灰底 + 左侧竖条,官方支持)<br>list = 无序列表(缩进 + 圆点,官方支持)<br>none = 不包裹'),
|
|
72
83
|
decimalsForward: Schema.number()
|
|
73
84
|
.default(2)
|
|
74
85
|
.min(0)
|
|
@@ -83,6 +94,12 @@ const Config = Schema.object({
|
|
|
83
94
|
|
|
84
95
|
// ===== 抓取与解析 =====
|
|
85
96
|
|
|
97
|
+
// dd373 的阿里云 WAF 挑战页特征(响应里是滑块验证组件,没有任何商品数据)
|
|
98
|
+
function looksLikeWafChallenge(html) {
|
|
99
|
+
const s = String(html || '')
|
|
100
|
+
return /aliyun-captcha|_waf_is_mobile|slideStyle|captcha-element/.test(s)
|
|
101
|
+
}
|
|
102
|
+
|
|
86
103
|
async function fetchRates(ctx, url, count) {
|
|
87
104
|
const limit = Math.max(1, Math.min(10, Number(count) || 5))
|
|
88
105
|
const response = await ctx.http.get(url, {
|
|
@@ -91,6 +108,9 @@ async function fetchRates(ctx, url, count) {
|
|
|
91
108
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
92
109
|
'Accept': 'text/html,application/xhtml+xml',
|
|
93
110
|
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
111
|
+
// ⚠️ dd373 的阿里云 WAF 会拦截「没有 Referer」的请求,直接返回滑块验证页(约 12KB,无任何商品数据)
|
|
112
|
+
// 实测结论:只有 Referer 是必需项,Cookie 无关(连伪造的 acw_tc 都能过)。缺了它整站必然解析失败
|
|
113
|
+
'Referer': DEFAULT_BUTTON_URL,
|
|
94
114
|
},
|
|
95
115
|
})
|
|
96
116
|
|
|
@@ -114,6 +134,10 @@ async function fetchRates(ctx, url, count) {
|
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
if (rates.length === 0) {
|
|
137
|
+
// 区分「被反爬拦截」和「页面真的改版了」——两者的排查方向完全不同
|
|
138
|
+
if (looksLikeWafChallenge(html)) {
|
|
139
|
+
throw new Error('请求被 dd373 的反爬验证页拦截(阿里云 WAF,返回了滑块验证页)。通常是请求过于频繁或缺少 Referer')
|
|
140
|
+
}
|
|
117
141
|
throw new Error('无法解析页面汇率数据,页面结构可能已变更')
|
|
118
142
|
}
|
|
119
143
|
|
|
@@ -198,6 +222,7 @@ function buildCardMarkdown(rates, timestamp, config) {
|
|
|
198
222
|
|
|
199
223
|
// 报价区排版。box 省略时取 config.ratesBox
|
|
200
224
|
// code = 代码块(灰色框 / 等宽 / 两列对齐)
|
|
225
|
+
// table = GFM 管道表格(带表头,客户端支持才渲染;不在 QQ 官方支持清单里)
|
|
201
226
|
// quote = 块引用(灰底 + 左侧竖条)
|
|
202
227
|
// list = 无序列表(无底色,缩进 + 圆点)
|
|
203
228
|
// none = 不加任何包裹
|
|
@@ -216,6 +241,15 @@ function buildRateBlock(rates, config, box) {
|
|
|
216
241
|
return ['```', ...body, '```']
|
|
217
242
|
}
|
|
218
243
|
|
|
244
|
+
if (mode === 'table') {
|
|
245
|
+
// 表头第二行的分隔行:单元格数量必须与表头一致,否则表格不成立
|
|
246
|
+
return [
|
|
247
|
+
'| # | 1元 兑 | 1神石 折 |',
|
|
248
|
+
'| --- | --- | --- |',
|
|
249
|
+
...rows.map((r, i) => `| ${i + 1} | ${r.fwd} 神石 | ${r.rev} 元 |`),
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
|
|
219
253
|
const body = rows.map(r => `1元 = ${r.fwd}神石 | 1神石 = ${r.rev}元`)
|
|
220
254
|
if (mode === 'quote') return body.map(l => '> ' + l)
|
|
221
255
|
if (mode === 'list') return body.map(l => '- ' + l)
|
|
@@ -254,8 +288,16 @@ function toPlainText(markdown, buttonUrl) {
|
|
|
254
288
|
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1: $2')
|
|
255
289
|
.replace(/^\s*>\s?/gm, '')
|
|
256
290
|
.replace(/[ \t]+\n/g, '\n')
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
|
|
292
|
+
// GFM 表格 → 纯文本:分隔行丢掉,数据行的竖线换成空格
|
|
293
|
+
out = out.split('\n').map((line) => {
|
|
294
|
+
const t = line.trim()
|
|
295
|
+
if (!t.startsWith('|')) return line
|
|
296
|
+
if (/^\|[\s:|-]*\|$/.test(t)) return '' // | --- | --- | 这种分隔行
|
|
297
|
+
return t.replace(/^\||\|$/g, '').split('|').map(c => c.trim()).filter(Boolean).join(' ')
|
|
298
|
+
}).join('\n')
|
|
299
|
+
|
|
300
|
+
out = out.replace(/\n{3,}/g, '\n\n').trim()
|
|
259
301
|
if (buttonUrl) out += `\n详情: ${buttonUrl}`
|
|
260
302
|
return out || ' '
|
|
261
303
|
}
|
|
@@ -409,8 +451,8 @@ function apply(ctx, config) {
|
|
|
409
451
|
}
|
|
410
452
|
})
|
|
411
453
|
|
|
412
|
-
//
|
|
413
|
-
ctx.command('bnsrate.box', '
|
|
454
|
+
// 排版对照:一条消息里同时给出各种报价区排版,在手机上挑一个填进 ratesBox
|
|
455
|
+
ctx.command('bnsrate.box', '报价区排版对照:一条消息看所有样式,挑一个填到 ratesBox')
|
|
414
456
|
.action(async ({ session }) => {
|
|
415
457
|
let data
|
|
416
458
|
try {
|
|
@@ -425,14 +467,8 @@ function apply(ctx, config) {
|
|
|
425
467
|
'',
|
|
426
468
|
'把下面最好看的那一项填到插件设置页的 ratesBox:',
|
|
427
469
|
]
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
quote: 'B. quote —— 块引用(灰底 + 左侧竖条 / 桌面端有颜色)',
|
|
431
|
-
list: 'C. list —— 无序列表(无底色 / 缩进 + 圆点)',
|
|
432
|
-
none: 'D. none —— 不包裹(纯文本行)',
|
|
433
|
-
}
|
|
434
|
-
for (const box of ['code', 'quote', 'list', 'none']) {
|
|
435
|
-
parts.push('', `## ${labels[box]}`, '', ...buildRateBlock(sample, config, box))
|
|
470
|
+
for (const box of RATE_BOXES) {
|
|
471
|
+
parts.push('', `## ${RATE_BOX_LABELS[box]}`, '', ...buildRateBlock(sample, config, box))
|
|
436
472
|
}
|
|
437
473
|
|
|
438
474
|
const markdown = parts.join('\n')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-bns-rate",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Fetch BNS Classic Divine Stone exchange rate from dd373.com, with QQ official Markdown card support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"koishi": {
|
|
19
19
|
"description": {
|
|
20
|
-
"zh": "从 dd373.com 获取剑灵怀旧服神石实时汇率,输入 神石/bnsrate/shenshi 即可查询最低比例。支持官方QQ机器人 Markdown 卡片:标题 + 横幅图 +
|
|
20
|
+
"zh": "从 dd373.com 获取剑灵怀旧服神石实时汇率,输入 神石/bnsrate/shenshi 即可查询最低比例。支持官方QQ机器人 Markdown 卡片:标题 + 横幅图 + 报价区支持代码块灰框/表格/引用/列表四种排版+ 跳转按钮,正文与按钮同一条消息;横幅图片、外框样式、配色、小数位均可在插件设置页配置。",
|
|
21
21
|
"en": "Fetch BNS Classic Divine Stone exchange rate from dd373.com. QQ official bot Markdown card: title, banner image, gray code-box rate table, jump button — all in a single message and fully configurable."
|
|
22
22
|
},
|
|
23
23
|
"service": {
|