dsh-line-bot 0.10.4 → 0.10.5
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/CHANGELOG.md +5 -0
- package/README.en.md +2 -1
- package/README.ja.md +2 -1
- package/README.ko.md +2 -1
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/lib/host.mjs +29 -3
- package/lib/webhook-server.mjs +37 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## 0.10.5
|
|
2
|
+
|
|
3
|
+
- 合併連續訊息:模型還在處理上一則時,使用者又補了下一句,會先排進佇列;這一輪結束後把補充內容合成同一段一起回應,不再每句各回一次。
|
|
4
|
+
- webhook 佇列改成 per-conversation 的 pending 清單,避免慢回覆期間的補充訊息被丟掉或觸發另一輪獨立回覆。
|
|
5
|
+
|
|
1
6
|
## 0.10.4
|
|
2
7
|
|
|
3
8
|
- Windows 語音引擎:優先選擇含 `openblas.dll` 的完整 CrispASR bundle,忽略舊安裝留下的裸 `crispasr.exe`(避免 `STATUS_DLL_NOT_FOUND`)。
|
package/README.en.md
CHANGED
|
@@ -14,6 +14,7 @@ Built in:
|
|
|
14
14
|
- **Without the dafeiyu plugin**: the plugin falls back to its own Traditional-Chinese editions of 大肥魚 / 阿茶 / 聽瀾, stored in the same `personas.json` and editable like custom personas. Selecting dafeiyu again switches back automatically.
|
|
15
15
|
- **Proactive messages**: a scheduler built into the plugin (no launchd). Each user gets an independent randomized countdown (between a minimum and maximum interval); it only speaks when the countdown is due and the user has been idle long enough. Configurable min/max interval, daily/monthly caps, minimum gap, quiet hours, idle time, unanswered limit (pause after that many unanswered proactive messages, reset when the user replies), target users and prompt. The Agent can reply `SKIP` when it is not a good moment.
|
|
16
16
|
- **Split replies**: optionally split a reply into up to five LINE bubbles by blank line (paragraph) or every newline, instead of one long bubble; the panel can change the mode or turn it off.
|
|
17
|
+
- **Merged follow-ups**: if the user sends another message while the model is still working, it is queued and merged into one follow-up turn, so the supplement is answered together instead of one reply per message.
|
|
17
18
|
- **Full settings panel**: every setting lives under Settings → LINE Bot and is stored in `<DSH_HOME>/line-bot/config.json` (mode 600).
|
|
18
19
|
|
|
19
20
|
> About "log in with LINE Business ID and have the panel create the bot": since 2024-09 LINE only allows creating a Messaging API channel manually in [LINE Official Account Manager](https://manager.line.biz/), with no public API; LINE Login only authenticates identity and cannot grant management rights over an Official Account. So the panel does not create the bot, but it gives you the correct entry links and automates everything after "you have the Channel ID / Secret" (issuing the token, setting the webhook, testing it).
|
|
@@ -268,4 +269,4 @@ The persona can attach a sticker to a text reply, or reply with just an image. I
|
|
|
268
269
|
|
|
269
270
|
- Host logic: `lib/*.mjs` (`util` / `config` / `line-api` / `persona` / `agent-runner` / `webhook-server` / `cloudflare` / `tunnel` / `nudge` / `tieba-sync` / `host`).
|
|
270
271
|
- Panel: `client.js` (registers `settings.section` via `window.__ModuleLoader__`).
|
|
271
|
-
- Entry point: `index.js` (mounts `/api/line-bot`, dev reload, cleanup on unload).
|
|
272
|
+
- Entry point: `index.js` (mounts `/api/line-bot`, dev reload, cleanup on unload).
|
package/README.ja.md
CHANGED
|
@@ -13,6 +13,7 @@ LINE 公式アカウント(Messaging API)を DeepSeek Harness につなぐ
|
|
|
13
13
|
- **LINE 認証情報**:Channel ID と Channel Secret を入力すれば access token を自動発行(`/v2/oauth/accessToken`、約 30 日、期限が近づくと自動更新)。既存の Channel Access Token を貼り付けることもできます。
|
|
14
14
|
- **大肥魚プラグインがない場合**:プラグイン内蔵の繁体字中国語版(大肥魚/阿茶/聽瀾)に自動で切り替わります。同じ `personas.json` に保存され、カスタムペルソナと同じように編集できます。大肥魚を入れ直すと自動でそちらに戻ります。
|
|
15
15
|
- **自発メッセージ**:プラグイン内蔵のスケジューラー(launchd 不要)。ユーザーごとに独立したランダムなカウントダウン(最短~最長間隔)を持ち、時間が来て相手の沈黙が十分長いときだけ話しかけます。最短/最長間隔、1 日/1 か月の上限、最低間隔、静かな時間帯、idle、連続未返信の上限(上限に達したら一時停止、相手が返信したらリセット)、対象ユーザー、プロンプトを設定できます。Agent が「今は違う」と判断したら `SKIP` を返して見送れます。
|
|
16
|
+
- **連続メッセージの結合**:モデルが前の返信を処理している間に追加のメッセージが届いた場合、キューに入れておき、そのターンが終わってからまとめて一度に返信します。
|
|
16
17
|
- **分割返信**:返信を空行(段落)または改行ごとに最大 5 つの LINE メッセージに分割できます。1 つの長い吹き出しに詰め込まずに送れます。パネルで方式の切り替えやオフも可能です。
|
|
17
18
|
- **充実した設定パネル**:設定はすべて「設定 → LINE Bot」にあり、`<DSH_HOME>/line-bot/config.json`(モード 600)に保存されます。
|
|
18
19
|
|
|
@@ -270,4 +271,4 @@ Bot がグループ(または複数人のルーム)に追加されたとき
|
|
|
270
271
|
|
|
271
272
|
- host ロジック:`lib/*.mjs`(`util`/`config`/`line-api`/`persona`/`agent-runner`/`webhook-server`/`cloudflare`/`tunnel`/`nudge`/`tieba-sync`/`host`)。
|
|
272
273
|
- パネル:`client.js`(`window.__ModuleLoader__` で `settings.section` を登録)。
|
|
273
|
-
- エントリーポイント:`index.js`(`/api/line-bot` のマウント、dev reload、アンロード時のクリーンアップ)。
|
|
274
|
+
- エントリーポイント:`index.js`(`/api/line-bot` のマウント、dev reload、アンロード時のクリーンアップ)。
|
package/README.ko.md
CHANGED
|
@@ -13,6 +13,7 @@ LINE 공식 계정(Messaging API)을 DeepSeek Harness에 연결하는 플러그
|
|
|
13
13
|
- **LINE 자격 증명**: Channel ID와 Channel Secret을 입력하면 access token을 자동 발급합니다(`/v2/oauth/accessToken`, 약 30일, 만료 전 자동 갱신). 기존 Channel Access Token을 붙여 넣어도 됩니다.
|
|
14
14
|
- **大肥魚 플러그인이 없을 때**: 플러그인 내장 번체 중국어 버전(大肥魚/阿茶/聽瀾)으로 자동 전환됩니다. 같은 `personas.json`에 저장되며 커스텀 페르소나처럼 편집할 수 있습니다. 大肥魚를 다시 설치하면 자동으로 원래 페르소나로 돌아갑니다.
|
|
15
15
|
- **능동 메시지**: 플러그인 내장 스케줄러(launchd 불필요). 사용자마다 독립적인 무작위 카운트다운(최소~최대 간격)을 가지며, 시간이 되고 상대의 침묵이 충분히 길 때만 말을 겁니다. 최소/최대 간격, 일일/월간 상한, 최소 간격, 조용한 시간대, idle, 연속 미응답 상한(도달 시 일시 중지, 상대가 답하면 초기화), 대상 사용자, 프롬프트를 설정할 수 있습니다. Agent가 지금은 아니라고 판단하면 `SKIP`을 반환해 건너뜁니다.
|
|
16
|
+
- **연속 메시지 병합**: 모델이 이전 답변을 처리하는 동안 다음 메시지가 오면 대기열에 넣고, 현재 턴이 끝난 뒤 보충 내용을 한 번에 합쳐 답합니다.
|
|
16
17
|
- **분할 답장**: 답장을 빈 줄(문단) 또는 줄바꿈마다 최대 5개의 LINE 메시지로 나눠 보낼 수 있습니다. 하나의 긴 말풍선에 몰아넣지 않습니다. 패널에서 방식을 바꾸거나 끌 수 있습니다.
|
|
17
18
|
- **전체 설정 패널**: 모든 설정이 "설정 → LINE Bot"에 있으며 `<DSH_HOME>/line-bot/config.json`(모드 600)에 저장됩니다.
|
|
18
19
|
|
|
@@ -268,4 +269,4 @@ Bot이 그룹(또는 다인 룸)에 추가되면:
|
|
|
268
269
|
|
|
269
270
|
- host 로직: `lib/*.mjs`(`util`/`config`/`line-api`/`persona`/`agent-runner`/`webhook-server`/`cloudflare`/`tunnel`/`nudge`/`tieba-sync`/`host`).
|
|
270
271
|
- 패널: `client.js`(`window.__ModuleLoader__`로 `settings.section` 등록).
|
|
271
|
-
- 진입점: `index.js`(`/api/line-bot` 마운트, dev reload, 언로드 정리).
|
|
272
|
+
- 진입점: `index.js`(`/api/line-bot` 마운트, dev reload, 언로드 정리).
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- **沒有大肥魚外掛時**:自動改用插件內建的繁體中文版本(大肥魚/阿茶/聽瀾),存在同一個 `personas.json`,可以像自訂人設一樣點開編輯;裝回大肥魚外掛後會自動切回外掛的人設。
|
|
15
15
|
- **主動傳訊**:插件內建排程器(不用 launchd),每位使用者各自倒數(最短~最長間隔隨機);可調最短/最長間隔、每日/每月上限、兩則間距、安靜時段、對方安靜多久才可打擾、連續未回覆上限(達上限暫停,對方回話後歸零)、指定對象與提示詞;Agent 覺得不適合時可以回 `SKIP` 跳過。
|
|
16
16
|
- **分段回覆**:可把回覆依空行(段落)或每個換行拆成多個 LINE 訊息框,最多 5 則,不再全部擠在同一則;面板可切換分段方式或關閉。
|
|
17
|
+
- **合併連續訊息**:模型還在處理上一則時,使用者又補了下一句,會先排隊,等這一輪結束後把補充內容合成一次一起回應,不會每句各回一次。
|
|
17
18
|
- **完整設定面板**:所有設定都在「設定 → LINE Bot」裡,存到 `<DSH_HOME>/line-bot/config.json`(權限 600)。
|
|
18
19
|
|
|
19
20
|
> 關於「用 LINE Business ID 登入、由面板代建 bot」:LINE 自 2024-09 起只允許在 [LINE Official Account Manager](https://manager.line.biz/) 手動建立 Messaging API channel,沒有公開 API;LINE Login 也只是身分驗證,不能取得官方帳號的管理權。所以面板不代建 bot,但會給你正確的入口連結,並把「拿到 Channel ID / Secret 之後」的所有步驟自動化(簽 token、設 webhook、測試)。
|
|
@@ -268,4 +269,4 @@ Bot 被加進群組(或多人聊天室)後:
|
|
|
268
269
|
|
|
269
270
|
- host 邏輯:`lib/*.mjs`(`util`/`config`/`line-api`/`persona`/`agent-runner`/`webhook-server`/`cloudflare`/`tunnel`/`nudge`/`tieba-sync`/`host`)。
|
|
270
271
|
- 面板:`client.js`(`window.__ModuleLoader__` 註冊 `settings.section`)。
|
|
271
|
-
- 進入點:`index.js`(掛 `/api/line-bot`、dev reload、卸載清理)。
|
|
272
|
+
- 進入點:`index.js`(掛 `/api/line-bot`、dev reload、卸載清理)。
|
package/README.zh-CN.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- **没有大肥鱼插件时**:自动改用插件内置的繁体中文版本(大肥鱼/阿茶/听澜),存在同一个 `personas.json`,可以像自定义人设一样点开编辑;装回大肥鱼插件后会自动切回插件的人设。
|
|
15
15
|
- **主动传讯**:插件内置排程器(不用 launchd),每位使用者各自倒数(最短~最长间隔随机);可调最短/最长间隔、每日/每月上限、两则间距、安静时段、对方安静多久才可打扰、连续未回复上限(达上限暂停,对方回话后归零)、指定对象与提示词;Agent 觉得不适合时可以回 `SKIP` 跳过。
|
|
16
16
|
- **分段回复**:可把回复依空行(段落)或每个换行拆成多个 LINE 消息框,最多 5 条,不再全部挤在同一条;面板可切换分段方式或关闭。
|
|
17
|
+
- **合并连续消息**:模型还在处理上一条时,用户又补了下一句,会先排队,等这一轮结束后把补充内容合并成一次一起回应,不会每句各回一次。
|
|
17
18
|
- **完整设置面板**:所有设置都在「设置 → LINE Bot」里,存到 `<DSH_HOME>/line-bot/config.json`(权限 600)。
|
|
18
19
|
|
|
19
20
|
> 关于「用 LINE Business ID 登录、由面板代建 bot」:LINE 自 2024-09 起只允许在 [LINE Official Account Manager](https://manager.line.biz/) 手动创建 Messaging API channel,没有公开 API;LINE Login 也只是身份验证,不能取得官方账号的管理权。所以面板不代建 bot,但会给你正确的入口链接,并把「拿到 Channel ID / Secret 之后」的所有步骤自动化(签 token、设 webhook、测试)。
|
|
@@ -268,4 +269,4 @@ Bot 被加入群组(或多人聊天室)后:
|
|
|
268
269
|
|
|
269
270
|
- host 逻辑:`lib/*.mjs`(`util`/`config`/`line-api`/`persona`/`agent-runner`/`webhook-server`/`cloudflare`/`tunnel`/`nudge`/`tieba-sync`/`host`)。
|
|
270
271
|
- 面板:`client.js`(`window.__ModuleLoader__` 注册 `settings.section`)。
|
|
271
|
-
- 进入点:`index.js`(挂 `/api/line-bot`、dev reload、卸载清理)。
|
|
272
|
+
- 进入点:`index.js`(挂 `/api/line-bot`、dev reload、卸载清理)。
|
package/lib/host.mjs
CHANGED
|
@@ -51,7 +51,7 @@ const { createVision } = visionMod
|
|
|
51
51
|
const { createTts } = ttsMod
|
|
52
52
|
const { chunkText, clip, inQuietWindow, jsonFail, jsonOk, maskSecret, readJsonBody, safeArray, sleep, readJsonSafe } = utilMod
|
|
53
53
|
|
|
54
|
-
export const VERSION = '0.10.
|
|
54
|
+
export const VERSION = '0.10.5'
|
|
55
55
|
|
|
56
56
|
// 環境變數提供的 Cloudflare API Token(方便用 shell 注入、不用把 token 寫進設定檔)。
|
|
57
57
|
// 設定檔裡的 token 優先;兩者都沒有時才用環境變數。
|
|
@@ -1695,6 +1695,32 @@ export async function create({ ctx, config: pluginConfig, logger: upstream }) {
|
|
|
1695
1695
|
store.updateState((state) => ({ ...state, lastEventAt: runtime.lastEventAt }))
|
|
1696
1696
|
const cfg = store.get()
|
|
1697
1697
|
if (cfg.enabled === false) return
|
|
1698
|
+
if (event?.type === 'message_batch') {
|
|
1699
|
+
const batch = Array.isArray(event.batch) ? event.batch.filter(Boolean) : []
|
|
1700
|
+
const mergeable = batch.length > 0
|
|
1701
|
+
&& batch.every((item) => item?.type === 'message' && item?.message?.type === 'text' && (!item?.source?.type || item.source.type === 'user'))
|
|
1702
|
+
if (!mergeable) {
|
|
1703
|
+
for (const item of batch) await handleEvent(item)
|
|
1704
|
+
return
|
|
1705
|
+
}
|
|
1706
|
+
const texts = batch.map((item) => String(item.message?.text || '').trim()).filter(Boolean)
|
|
1707
|
+
if (texts.length === 0) return
|
|
1708
|
+
if (texts.some((item) => PERSONA_CMD.test(item))) {
|
|
1709
|
+
for (const item of batch) await handleEvent(item)
|
|
1710
|
+
return
|
|
1711
|
+
}
|
|
1712
|
+
const last = batch[batch.length - 1]
|
|
1713
|
+
event = {
|
|
1714
|
+
...last,
|
|
1715
|
+
message: {
|
|
1716
|
+
...last.message,
|
|
1717
|
+
type: 'text',
|
|
1718
|
+
text: `(使用者在上一則還沒處理完時又補充了 ${texts.length} 則訊息;請把下面這些話視為同一段,一起回應,不要逐句分開回。)\n${texts.join('\n')}`,
|
|
1719
|
+
},
|
|
1720
|
+
__mergedCount: texts.length,
|
|
1721
|
+
}
|
|
1722
|
+
logger.info(`合併 ${texts.length} 則連續訊息後一起處理`)
|
|
1723
|
+
}
|
|
1698
1724
|
const type = event?.type
|
|
1699
1725
|
const source = event?.source || {}
|
|
1700
1726
|
const userId = source.userId
|
|
@@ -1838,10 +1864,10 @@ export async function create({ ctx, config: pluginConfig, logger: upstream }) {
|
|
|
1838
1864
|
if (!target) return state
|
|
1839
1865
|
return {
|
|
1840
1866
|
...state,
|
|
1841
|
-
totalMessages: (Number(state.totalMessages) || 0) + 1,
|
|
1867
|
+
totalMessages: (Number(state.totalMessages) || 0) + (Number(event.__mergedCount) || 1),
|
|
1842
1868
|
[bucketName]: {
|
|
1843
1869
|
...map,
|
|
1844
|
-
[conversationKey]: { ...target, messages: (Number(target.messages) || 0) + 1, lastAt: Date.now(), lastUserAt: Date.now() },
|
|
1870
|
+
[conversationKey]: { ...target, messages: (Number(target.messages) || 0) + (Number(event.__mergedCount) || 1), lastAt: Date.now(), lastUserAt: Date.now() },
|
|
1845
1871
|
},
|
|
1846
1872
|
}
|
|
1847
1873
|
})
|
package/lib/webhook-server.mjs
CHANGED
|
@@ -43,17 +43,43 @@ export function createWebhookServer({ getConfig, logger, dispatch, getMedia, get
|
|
|
43
43
|
pruneDedupe()
|
|
44
44
|
}
|
|
45
45
|
const key = event?.source?.userId || event?.source?.groupId || event?.source?.roomId || 'unknown'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
let queue = queues.get(key)
|
|
47
|
+
if (!queue) {
|
|
48
|
+
queue = { pending: [], running: false }
|
|
49
|
+
queues.set(key, queue)
|
|
50
|
+
}
|
|
51
|
+
queue.pending.push(event)
|
|
52
|
+
if (queue.running) {
|
|
53
|
+
logger?.info?.(`${key} 還在處理上一則,先排入合併(目前 ${queue.pending.length} 則)`)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
queue.running = true
|
|
57
|
+
void drain(key, queue)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function drain(key, queue) {
|
|
61
|
+
try {
|
|
62
|
+
while (queue.pending.length > 0) {
|
|
63
|
+
const first = queue.pending.shift()
|
|
64
|
+
await dispatch(first)
|
|
65
|
+
if (queue.pending.length > 0) {
|
|
66
|
+
const batch = queue.pending.splice(0)
|
|
67
|
+
logger?.info?.(`合併 ${batch.length} 則補充訊息(${key})`)
|
|
68
|
+
await dispatch({ type: 'message_batch', batch })
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
lastError = error?.message || String(error)
|
|
73
|
+
logger?.error?.(`處理事件失敗:${lastError}`)
|
|
74
|
+
} finally {
|
|
75
|
+
queue.running = false
|
|
76
|
+
if (queue.pending.length > 0) {
|
|
77
|
+
queue.running = true
|
|
78
|
+
void drain(key, queue)
|
|
79
|
+
} else {
|
|
80
|
+
queues.delete(key)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
async function handle(req, res) {
|