lark-relay 0.6.2 → 0.6.3
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 +5 -2
- package/lib/collect.js +6 -19
- package/lib/help.js +4 -2
- package/lib/larkcli.js +3 -13
- package/lib/status.js +4 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,8 +23,10 @@ lark-relay take --app <app> --chats oc_xxx --render text
|
|
|
23
23
|
lark-relay status
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
采集在启动时读取 `lark-cli profile list
|
|
27
|
-
|
|
26
|
+
采集在启动时读取 `lark-cli profile list`,每个未被 `--exclude` 排除的 profile 启动一个 bot consumer
|
|
27
|
+
user 未登录或授权过期不影响 bot 采集,bot 凭据和权限由 `lark-cli event consume --as bot` 检查
|
|
28
|
+
认证或连接失败会记录 CLI 错误并独立退避重试,无需采集的 profile 用 `--exclude` 排除
|
|
29
|
+
新增或移除 profile 后需重启采集
|
|
28
30
|
同一 app 的事件总线只允许一个实例,本地互斥锁按 `LARK_RELAY_HOME` 隔离
|
|
29
31
|
|
|
30
32
|
## 取批规则
|
|
@@ -79,6 +81,7 @@ lark-relay status
|
|
|
79
81
|
日志直写 stderr,每行带 ISO 时间戳,不做轮转
|
|
80
82
|
需要缩减日志时截断文件,不要删除或替换 launchd 持有的文件
|
|
81
83
|
`status` 中 collect running 只说明主进程在跑,各 app 是否连接成功需查看采集日志
|
|
84
|
+
`collect.profiles` 列出已配置 profile,不表示正在采集,也不反映 collect 的 `--exclude`
|
|
82
85
|
|
|
83
86
|
## 许可
|
|
84
87
|
|
package/lib/collect.js
CHANGED
|
@@ -139,37 +139,24 @@ async function runCollect(opts) {
|
|
|
139
139
|
return 1
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
const chosen =
|
|
143
|
-
const skipped =
|
|
144
|
-
for (const p of profiles) {
|
|
145
|
-
if (exclude.has(p.name)) {
|
|
146
|
-
skipped.push(`${p.name} [excluded]`)
|
|
147
|
-
continue
|
|
148
|
-
}
|
|
149
|
-
// expired = 授权过期,not-authed = 从未 login。两者都永久失败,重试是死循环。
|
|
150
|
-
// 用户重新 login 后下次启动自动纳入。
|
|
151
|
-
if (!p.usable) {
|
|
152
|
-
skipped.push(`${p.name} [${p.reason}]`)
|
|
153
|
-
continue
|
|
154
|
-
}
|
|
155
|
-
chosen.push(p)
|
|
156
|
-
}
|
|
142
|
+
const chosen = profiles.filter((name) => !exclude.has(name))
|
|
143
|
+
const skipped = profiles.filter((name) => exclude.has(name))
|
|
157
144
|
|
|
158
145
|
if (skipped.length) {
|
|
159
|
-
logLine(
|
|
146
|
+
logLine(`跳过 --exclude: ${skipped.join(' ')}`)
|
|
160
147
|
}
|
|
161
148
|
if (!chosen.length) {
|
|
162
|
-
logLine('error:
|
|
149
|
+
logLine('error: 没有待采集的 profile,检查 `lark-cli profile list` 和 --exclude')
|
|
163
150
|
return 1
|
|
164
151
|
}
|
|
165
152
|
|
|
166
153
|
logLine(
|
|
167
154
|
`lark-relay-collect: ${chosen.length}/${profiles.length} profiles → ${paths.store}\n` +
|
|
168
|
-
` ${chosen.
|
|
155
|
+
` ${chosen.join(' ')}\n` +
|
|
169
156
|
` event_key=${EVENT_KEY} store 保留 ${opts.retain}天`,
|
|
170
157
|
)
|
|
171
158
|
|
|
172
|
-
const collectors = chosen.map((
|
|
159
|
+
const collectors = chosen.map((name) => new AppCollector(name))
|
|
173
160
|
for (const c of collectors) c.start()
|
|
174
161
|
|
|
175
162
|
// 只回收超期事件,保留消费者的断点
|
package/lib/help.js
CHANGED
|
@@ -37,8 +37,9 @@ const COLLECT_HELP = `lark-relay-collect -- 常驻收集 im.message.receive_v1
|
|
|
37
37
|
lark-relay-collect
|
|
38
38
|
lark-relay status
|
|
39
39
|
|
|
40
|
-
启动时读取 lark-cli profile list
|
|
41
|
-
|
|
40
|
+
启动时读取 lark-cli profile list,每个未被 --exclude 排除的 profile 启动一个 bot consumer
|
|
41
|
+
不依赖 user 登录态,bot 凭据和权限由 lark-cli 检查;失败会记录并退避重试
|
|
42
|
+
新增或移除 profile 后需重启采集
|
|
42
43
|
每 app 独立退避重连,断线期间的消息无法回放
|
|
43
44
|
同一 app 只允许一个事件总线实例,接管前先停止现有采集
|
|
44
45
|
|
|
@@ -64,6 +65,7 @@ const STATUS_HELP = `lark-relay status -- 采集状态、事件数量和消费
|
|
|
64
65
|
lark-relay status --json
|
|
65
66
|
|
|
66
67
|
消费者以持锁进程是否存活区分在跑/未运行,未运行的断点仍保留
|
|
68
|
+
profiles 列出已配置项,不表示正在采集,也不反映 collect 的 --exclude
|
|
67
69
|
collect running 只表示主进程在跑,各 app 的连接状态需查看采集日志`
|
|
68
70
|
|
|
69
71
|
module.exports = { TAKE_HELP, COLLECT_HELP, STATUS_HELP }
|
package/lib/larkcli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
// 唯一外部依赖:spawn lark-cli
|
|
3
|
+
// 唯一外部依赖:spawn lark-cli,通过公开命令读取配置
|
|
4
4
|
const { spawn, execFile } = require('child_process')
|
|
5
5
|
|
|
6
6
|
const CLI = process.env.LARK_CLI_BIN || 'lark-cli'
|
|
@@ -23,20 +23,10 @@ function run(args, opts = {}) {
|
|
|
23
23
|
})
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
//
|
|
26
|
+
// 只枚举配置;bot 认证和权限由 event consume --as bot 检查
|
|
27
27
|
async function listProfiles() {
|
|
28
28
|
const { stdout } = await run(['profile', 'list'])
|
|
29
|
-
|
|
30
|
-
return arr.map((p) => {
|
|
31
|
-
let reason = null
|
|
32
|
-
if (p.tokenStatus === 'expired') reason = 'expired'
|
|
33
|
-
else if (!p.user) reason = 'not-authed'
|
|
34
|
-
return {
|
|
35
|
-
name: p.name,
|
|
36
|
-
usable: reason === null,
|
|
37
|
-
reason,
|
|
38
|
-
}
|
|
39
|
-
})
|
|
29
|
+
return JSON.parse(stdout).map((p) => p.name)
|
|
40
30
|
}
|
|
41
31
|
|
|
42
32
|
// 保持 stdin 管道打开,stdout NDJSON 由 collect 原子写入
|
package/lib/status.js
CHANGED
|
@@ -73,16 +73,13 @@ async function buildStatus() {
|
|
|
73
73
|
try {
|
|
74
74
|
profiles = await larkcli.listProfiles()
|
|
75
75
|
} catch {}
|
|
76
|
-
const expired = profiles.filter((p) => !p.usable).map((p) => `${p.name}[${p.reason}]`)
|
|
77
|
-
const usable = profiles.filter((p) => p.usable).map((p) => p.name)
|
|
78
|
-
|
|
79
76
|
const byApp = new Map()
|
|
80
77
|
for (const c of listCursors()) {
|
|
81
78
|
if (!byApp.has(c.app)) byApp.set(c.app, [])
|
|
82
79
|
byApp.get(c.app).push(c)
|
|
83
80
|
}
|
|
84
81
|
|
|
85
|
-
const apps = [...new Set([...store.listApps(), ...
|
|
82
|
+
const apps = [...new Set([...store.listApps(), ...profiles])].sort()
|
|
86
83
|
const rows = apps.map((app) => {
|
|
87
84
|
const latest = store.latestEntry(app)
|
|
88
85
|
const ev = latest ? store.readEvent(latest) : null
|
|
@@ -98,8 +95,7 @@ async function buildStatus() {
|
|
|
98
95
|
collect: {
|
|
99
96
|
state,
|
|
100
97
|
pid,
|
|
101
|
-
|
|
102
|
-
expired,
|
|
98
|
+
profiles,
|
|
103
99
|
hint: `sudo launchctl bootstrap system /Library/LaunchDaemons/${COLLECT_LABEL}.plist`,
|
|
104
100
|
},
|
|
105
101
|
apps: rows,
|
|
@@ -124,11 +120,8 @@ function formatStatus(s) {
|
|
|
124
120
|
let head = `collect ${dot} ${s.collect.state === 'active' ? 'running' : s.collect.state}`
|
|
125
121
|
const bits = []
|
|
126
122
|
if (s.collect.pid) bits.push(`pid ${s.collect.pid}`)
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
if (bits.length) head += ` (${bits.join(', ')}`
|
|
130
|
-
if (s.collect.expired.length) head += `; skipped: ${s.collect.expired.join(' ')}`
|
|
131
|
-
if (bits.length) head += ')'
|
|
123
|
+
if (s.collect.profiles.length) bits.push(`${s.collect.profiles.length} configured profiles`)
|
|
124
|
+
if (bits.length) head += ` (${bits.join(', ')})`
|
|
132
125
|
out.push(head)
|
|
133
126
|
if (s.collect.state !== 'active') {
|
|
134
127
|
out.push(' ⚠ collect 没在跑 —— 此刻的消息会永久丢失,不是延迟送达')
|