sintbot 1.4.7 → 1.4.8

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/src/upgrade.js ADDED
@@ -0,0 +1,58 @@
1
+ // 版本升级群播报:发版部署重启后,各绑定群收到一条版本更新消息(此前升级群内无感)。
2
+ // daemon 启动时对比 state 里记录的上个版本号,变了就用 CHANGELOG.json(与控制台
3
+ // 「版本历史」同一事实源)的新增条目拼文案。纯函数,bin/sbot.js 接线;零依赖。
4
+
5
+ // 产品在群成员面前的名字;顶层 `brand` 配置覆盖。⚠ 改默认值 = 替所有没配 `brand` 的部署改名。
6
+ const DEFAULT_BRAND = 'SintBot'
7
+
8
+ // "0.3.10" vs "0.3.9" 逐段数值比较(字符串比较会把 .10 排在 .9 前);非法段按 0。
9
+ function cmpVersion(a, b) {
10
+ const pa = String(a || '').split('.').map((x) => parseInt(x, 10) || 0)
11
+ const pb = String(b || '').split('.').map((x) => parseInt(x, 10) || 0)
12
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
13
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0)
14
+ }
15
+ return 0
16
+ }
17
+
18
+ // 升级播报文案;无需播报返回 null:首次运行(无基线,播了像误报)/ 版本没变 / 降级(回滚
19
+ // 不宣传)。列出 (last, current] 区间的全部版本条目 —— 跨多版升级不漏说,新版在前;
20
+ // 条目只列标题(t),详情引导去控制台「版本历史」,群消息保持一屏内。
21
+ function upgradeSpan(lastVersion, currentVersion, changelog = []) {
22
+ if (!lastVersion || !currentVersion) return null
23
+ if (cmpVersion(currentVersion, lastVersion) <= 0) return null
24
+ return (Array.isArray(changelog) ? changelog : []).filter((e) =>
25
+ e && cmpVersion(e.version, lastVersion) > 0 && cmpVersion(e.version, currentVersion) <= 0)
26
+ }
27
+
28
+ function buildUpgradeNotice(lastVersion, currentVersion, changelog = [], brand = DEFAULT_BRAND) {
29
+ const span = upgradeSpan(lastVersion, currentVersion, changelog)
30
+ if (!span) return null
31
+ const L = [`🚀 ${brand || DEFAULT_BRAND} 已升级:v${lastVersion} → v${currentVersion}`]
32
+ for (const e of span) {
33
+ L.push(`【v${e.version}${e.name ? ' · ' + e.name : ''}】`)
34
+ for (const it of e.items || []) if (it && it.t) L.push(`· ${it.t}`)
35
+ }
36
+ L.push('详情见控制台「设置 → 版本历史」')
37
+ return L.join('\n')
38
+ }
39
+
40
+ function buildUpgradeNoticeParts(lastVersion, currentVersion, changelog = [], brand = DEFAULT_BRAND) {
41
+ const span = upgradeSpan(lastVersion, currentVersion, changelog)
42
+ if (!span) return null
43
+ const one = span.length === 1 ? span[0] : null
44
+ const body = span.map((e) => [
45
+ `**v${e.version}${e.name ? ' · ' + e.name : ''}**`,
46
+ ...(e.items || []).filter((it) => it && it.t).map((it) => `· ${it.t}`),
47
+ ].join('\n')).join('\n\n')
48
+ return {
49
+ tone: 'upgrade',
50
+ title: `${brand || DEFAULT_BRAND} 已升级到 v${currentVersion}`,
51
+ subtitle: `v${lastVersion} → v${currentVersion}` +
52
+ (one && one.name ? ` · ${one.name}` : (span.length > 1 ? ` · 共 ${span.length} 个版本` : '')),
53
+ body,
54
+ footer: '详情见控制台「设置 → 版本历史」',
55
+ }
56
+ }
57
+
58
+ module.exports = { cmpVersion, upgradeSpan, buildUpgradeNotice, buildUpgradeNoticeParts, DEFAULT_BRAND }
@@ -0,0 +1,62 @@
1
+ 'use strict'
2
+
3
+ // Startup upgrade announcement. After a version change each configured group
4
+ // that is neither paused nor held receives one notice card (text where the
5
+ // chat has no card capability), built from CHANGELOG.json like the console's
6
+ // version history. First run and downgrades only record the baseline; so does
7
+ // a start with the console's announcement preference turned off.
8
+ //
9
+ // The new baseline is recorded before anything is sent: a crash during the
10
+ // broadcast loses the announcement instead of repeating it on the next start.
11
+ // Every failure is logged per group and never reaches startup.
12
+ const fs = require('node:fs')
13
+ const path = require('node:path')
14
+ const { cmpVersion } = require('./upgrade')
15
+ const { deliverUpgradeNotice } = require('./platform/noticesend')
16
+ const { redact } = require('./core/redact')
17
+
18
+ function readChangelog(file = path.join(__dirname, '..', 'CHANGELOG.json')) {
19
+ try {
20
+ const value = JSON.parse(fs.readFileSync(file, 'utf8'))
21
+ return Array.isArray(value) ? value : []
22
+ } catch { return [] }
23
+ }
24
+
25
+ function announceUpgradeOnStart({ store, cfg = {}, platform, changelog = [], currentVersion,
26
+ heldGroups = [], log = () => {}, deliver = deliverUpgradeNotice } = {}) {
27
+ const skipped = state => ({ state, chats: [], done: Promise.resolve([]) })
28
+ let last
29
+ try {
30
+ last = store.getLastVersion()
31
+ if (!currentVersion || last === currentVersion) return skipped('unchanged')
32
+ store.setLastVersion(currentVersion)
33
+ } catch (error) {
34
+ // Without a durable baseline every restart would announce again.
35
+ log(`升级播报: 版本基线没有记下,本次不播报: ${error && error.message}`)
36
+ return skipped('baseline-failed')
37
+ }
38
+ if (!last) return skipped('first-run')
39
+ if (cmpVersion(currentVersion, last) <= 0) return skipped('downgrade')
40
+ if (cfg.ui?.announceUpdates === false) return skipped('disabled')
41
+ if (!platform) { log('升级播报: 平台未连接,本次不播报'); return skipped('no-platform') }
42
+ const held = new Set(heldGroups instanceof Map ? heldGroups.keys() : heldGroups)
43
+ const chats = []
44
+ const deliveries = []
45
+ for (const [chatId, g] of Object.entries(cfg.groups || {})) {
46
+ if (!g || g.paused || held.has(chatId)) continue // 暂停 = 别打扰;被挂起的群不发
47
+ chats.push(chatId)
48
+ deliveries.push(Promise.resolve()
49
+ .then(() => deliver(platform, chatId, last, currentVersion, changelog, cfg.brand,
50
+ { redact: s => redact(s, { pii: g.redactPii !== false }), log }))
51
+ .then(r => {
52
+ if (r && r.result == null) log(`升级播报未送达(${chatId}):平台确认没发出去`)
53
+ return { chatId, mode: r ? r.mode : null, delivered: !!(r && r.result != null) }
54
+ }, error => {
55
+ log(`升级播报(${chatId}): ${error && error.message}`)
56
+ return { chatId, mode: null, delivered: false }
57
+ }))
58
+ }
59
+ return { state: 'announced', chats, done: Promise.all(deliveries) }
60
+ }
61
+
62
+ module.exports = { announceUpgradeOnStart, readChangelog }