dsh-line-bot 0.9.0 → 0.9.1
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 +7 -0
- package/client.js +3 -3
- package/lib/host.mjs +40 -9
- package/lib/tieba-sync.mjs +8 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
- 貼吧學習同步的目標下拉現在也包含「插件自訂人設」(`local:`);選自訂人設時會寫入 `<DSH_HOME>/line-bot/persona-overlays/<id>.md` 覆蓋層,每輪注入。
|
|
5
|
+
- 讀不到大肥魚 HTTP bridge 時,改成直接讀取 `<DSH_HOME>/storages/dsh-persona-dafeiyu/personas.json` 並列出內建人設,不再讓同步目標變空。
|
|
6
|
+
- 修正本機自訂人設同步完成後回報的模式(應為 overlay)。
|
|
7
|
+
- 貼吧梗只從梗類貼吧匯入(一般回覆碎片不再被當成梗);內建種子擴充到 24 則。
|
|
8
|
+
- 明確要求角色在主人說「不要圖」時不要加表情包標記。
|
|
9
|
+
|
|
3
10
|
## 0.9.0
|
|
4
11
|
- 玩梗:新增文字梗庫(內建種子、貼吧高讚金句、wanjiqi-meme 彈幕梗),面板可個別開關來源、調整每輪注入數量、新增/刪除梗;每輪只餵幾條給角色,並限制一次回覆最多用一個。
|
|
5
12
|
- 貼吧學習可寫入內建人設:大肥魚、阿茶、听澜等內建人設不能改原文,改成寫入 `<DSH_HOME>/line-bot/persona-overlays/<id>.md` 覆蓋層,每輪注入、不用重載;自訂人設仍直接改本文。
|
package/client.js
CHANGED
|
@@ -759,7 +759,7 @@ window.__ModuleLoader__.load({
|
|
|
759
759
|
const selectedPersona = personaCatalog.find((item) => item.id === personaCfg.id) || null
|
|
760
760
|
const selectedName = selectedPersona?.name || personaCfg.id || '大肥魚'
|
|
761
761
|
const selectedIsLocal = String(personaCfg.id || '').startsWith('local:')
|
|
762
|
-
const syncPersonaOptions =
|
|
762
|
+
const syncPersonaOptions = personaCatalog.slice()
|
|
763
763
|
const syncTarget = syncCfg.targetPersonaId || (syncPersonaOptions[0] && syncPersonaOptions[0].id) || ''
|
|
764
764
|
const bot = status.line?.bot || null
|
|
765
765
|
const webhook = status.line?.webhook || null
|
|
@@ -1077,7 +1077,7 @@ window.__ModuleLoader__.load({
|
|
|
1077
1077
|
onChange: (event) => update({ agent: { model: event.target.value } }),
|
|
1078
1078
|
}),
|
|
1079
1079
|
h('datalist', { id: 'line-bot-model-options' },
|
|
1080
|
-
((modelProviders.find((item) => item.id === (draft.agent.provider || agentStatus.defaultProvider)) || {}).models || []).map((item) => h('option', { key: item.id, value: item.id }, item.name))),
|
|
1080
|
+
((modelProviders.find((item) => item.id === (draft.agent.provider || agentStatus.defaultProvider)) || {}).models || []).map((item) => h('option', { key: item.id, value: item.id }, item.name + (String(item.id).startsWith('local:') ? '(插件自訂)' : (item.builtin ? '(內建)' : ''))))),
|
|
1081
1081
|
),
|
|
1082
1082
|
h(Field, { label: '推理強度(選填)', hint: '留空=跟隨 provider 預設' },
|
|
1083
1083
|
h(Text, { value: draft.agent.reasoningEffort || '', onChange: (value) => update({ agent: { reasoningEffort: value } }), placeholder: 'max / high / medium' })),
|
|
@@ -1160,7 +1160,7 @@ window.__ModuleLoader__.load({
|
|
|
1160
1160
|
{ id: sectionDomId('sync'), order: 6, title: '貼吧學習同步', desc: '把「刷貼吧」每天演化學到的語氣樣本,寫進上方人設。自訂人設會直接改人設本文;內建人設(大肥魚、阿茶、听澜)不能改原文,會寫進插件自己的覆蓋層,每輪注入、不用重載。' },
|
|
1161
1161
|
h(Toggle, { checked: syncCfg.enabled !== false, onChange: (value) => update({ sync: { enabled: value } }), label: '開啟自動同步', hint: '每小時檢查一次;貼吧人設版本有變才寫入(寫入前會備份 personas.json)' }),
|
|
1162
1162
|
h('div', { style: S.grid },
|
|
1163
|
-
h(Field, { label: '同步到哪個人設', hint: '
|
|
1163
|
+
h(Field, { label: '同步到哪個人設', hint: '內建人設與插件自訂人設使用覆蓋層;大肥魚的自訂人設直接改本文' },
|
|
1164
1164
|
h('select', { value: syncTarget, onChange: (event) => update({ sync: { targetPersonaId: event.target.value } }), style: S.input },
|
|
1165
1165
|
syncPersonaOptions.length
|
|
1166
1166
|
? syncPersonaOptions.map((item) => h('option', { key: item.id, value: item.id }, item.name))
|
package/lib/host.mjs
CHANGED
|
@@ -28,7 +28,7 @@ const stickersMod = await import(withVersion('./stickers.mjs'))
|
|
|
28
28
|
const memeSourceMod = await import(withVersion('./meme-source.mjs'))
|
|
29
29
|
const stickerImageMod = await import(withVersion('./sticker-image.mjs'))
|
|
30
30
|
const utilMod = await import(withVersion('./util.mjs'))
|
|
31
|
-
const { createConfigStore, publicConfig, dataDir } = configMod
|
|
31
|
+
const { createConfigStore, publicConfig, dataDir, dshHome } = configMod
|
|
32
32
|
const { createPersonaClient } = personaMod
|
|
33
33
|
const { createAgentRunner } = runnerMod
|
|
34
34
|
const { createWebhookServer } = webhookMod
|
|
@@ -43,9 +43,9 @@ const { createJokeStore } = jokesMod
|
|
|
43
43
|
const { createStickerStore } = stickersMod
|
|
44
44
|
const { createMemeSource } = memeSourceMod
|
|
45
45
|
const { createStickerImages } = stickerImageMod
|
|
46
|
-
const { chunkText, clip, inQuietWindow, jsonFail, jsonOk, maskSecret, readJsonBody, safeArray, sleep } = utilMod
|
|
46
|
+
const { chunkText, clip, inQuietWindow, jsonFail, jsonOk, maskSecret, readJsonBody, safeArray, sleep, readJsonSafe } = utilMod
|
|
47
47
|
|
|
48
|
-
export const VERSION = '0.9.
|
|
48
|
+
export const VERSION = '0.9.1'
|
|
49
49
|
|
|
50
50
|
// 環境變數提供的 Cloudflare API Token(方便用 shell 注入、不用把 token 寫進設定檔)。
|
|
51
51
|
// 設定檔裡的 token 優先;兩者都沒有時才用環境變數。
|
|
@@ -161,17 +161,17 @@ export async function create({ ctx, config: pluginConfig, logger: upstream }) {
|
|
|
161
161
|
} catch (error) {
|
|
162
162
|
logger.warn(`請大肥魚讓開失敗(自訂人設仍會注入):${errorText(error)}`)
|
|
163
163
|
}
|
|
164
|
-
return { persona: null, personaText: resolved.text, source: 'local', name: resolved.name }
|
|
164
|
+
return { persona: null, personaText: resolved.text, source: 'local', name: resolved.name, selection: resolved.id }
|
|
165
165
|
}
|
|
166
166
|
if (resolved.kind === 'dafeiyu') {
|
|
167
|
-
return { persona: resolved.id, personaText: null, source: 'dafeiyu', name: '' }
|
|
167
|
+
return { persona: resolved.id, personaText: null, source: 'dafeiyu', name: '', selection: resolved.id }
|
|
168
168
|
}
|
|
169
|
-
return { persona: null, personaText: null, source: '', name: '' }
|
|
169
|
+
return { persona: null, personaText: null, source: '', name: '', selection: '' }
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
// 內建人設(大肥魚等)不能改原文,貼吧同步會寫在覆蓋層;每輪注入在 persona 之後。
|
|
173
173
|
function personaOverlayText(applied) {
|
|
174
|
-
const id = applied?.persona
|
|
174
|
+
const id = applied?.selection || applied?.persona
|
|
175
175
|
if (!id) return ''
|
|
176
176
|
try {
|
|
177
177
|
return readPersonaOverlay(id)
|
|
@@ -1675,6 +1675,33 @@ export async function create({ ctx, config: pluginConfig, logger: upstream }) {
|
|
|
1675
1675
|
|
|
1676
1676
|
// ── 人設摘要(給面板) ───────────────────────────────────────────────────
|
|
1677
1677
|
let personaCache = { at: 0, value: null }
|
|
1678
|
+
function readDafeiyuStoreFallback() {
|
|
1679
|
+
const builtins = [
|
|
1680
|
+
{ id: 'dafeiyu', name: '大肥魚', chars: 635 },
|
|
1681
|
+
{ id: 'acha', name: '阿茶', chars: 979 },
|
|
1682
|
+
{ id: 'tinglan', name: '听澜', chars: 1738 },
|
|
1683
|
+
].map((item) => ({ ...item, builtin: true, enabled: true, description: '', source: 'dafeiyu' }))
|
|
1684
|
+
const list = builtins
|
|
1685
|
+
try {
|
|
1686
|
+
const file = path.join(dshHome(), 'storages', 'dsh-persona-dafeiyu', 'personas.json')
|
|
1687
|
+
const raw = readJsonSafe(file, null)
|
|
1688
|
+
for (const item of raw?.personas || []) {
|
|
1689
|
+
const id = String(item?.id || '').trim()
|
|
1690
|
+
if (!id) continue
|
|
1691
|
+
list.push({
|
|
1692
|
+
id,
|
|
1693
|
+
name: String(item.name || id),
|
|
1694
|
+
builtin: false,
|
|
1695
|
+
enabled: true,
|
|
1696
|
+
chars: String(item.text || '').length,
|
|
1697
|
+
description: '直接從 personas.json 讀取',
|
|
1698
|
+
source: 'dafeiyu',
|
|
1699
|
+
})
|
|
1700
|
+
}
|
|
1701
|
+
} catch { /* 讀不到就只列內建 */ }
|
|
1702
|
+
return list
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1678
1705
|
async function getPersonaSummary({ force = false } = {}) {
|
|
1679
1706
|
const cfg = store.get()
|
|
1680
1707
|
if (!force && personaCache.value && Date.now() - personaCache.at < 5000) return personaCache.value
|
|
@@ -1722,7 +1749,11 @@ export async function create({ ctx, config: pluginConfig, logger: upstream }) {
|
|
|
1722
1749
|
]
|
|
1723
1750
|
} catch (error) {
|
|
1724
1751
|
summary.error = errorText(error)
|
|
1725
|
-
|
|
1752
|
+
const fallback = readDafeiyuStoreFallback()
|
|
1753
|
+
summary.list = fallback
|
|
1754
|
+
summary.mode = 'unknown'
|
|
1755
|
+
summary.catalog = [...fallback, ...local]
|
|
1756
|
+
summary.warning = '讀不到大肥魚外掛的 HTTP bridge,已直接讀取 personas.json;清單可能較舊,寫入後請重啟大肥魚外掛。'
|
|
1726
1757
|
}
|
|
1727
1758
|
const selected = summary.catalog.find((item) => item.id === cfg.persona.id)
|
|
1728
1759
|
summary.selectedName = selected?.name || ''
|
|
@@ -1730,7 +1761,7 @@ export async function create({ ctx, config: pluginConfig, logger: upstream }) {
|
|
|
1730
1761
|
summary.warning = selectedSource === 'local'
|
|
1731
1762
|
? `找不到自訂人設「${cfg.persona.id}」,LINE 會改用預設人設。`
|
|
1732
1763
|
: `找不到人設「${cfg.persona.id}」,LINE 會退回大肥魚的全域人設。`
|
|
1733
|
-
} else if (selectedSource === 'dafeiyu' && cfg.persona.enforce && summary.mode !== 'session') {
|
|
1764
|
+
} else if (!summary.error && selectedSource === 'dafeiyu' && cfg.persona.enforce && summary.mode !== 'session') {
|
|
1734
1765
|
summary.warning = `大肥魚目前是「${summary.mode || '未知'}」模式;LINE 的逐 session 人設指定要切到 session 模式才會生效。`
|
|
1735
1766
|
}
|
|
1736
1767
|
personaCache = { at: Date.now(), value: summary }
|
package/lib/tieba-sync.mjs
CHANGED
|
@@ -223,8 +223,9 @@ export function createTiebaSync({ ctx, store, logger, getCatalog }) {
|
|
|
223
223
|
const list = await catalog()
|
|
224
224
|
const info = list.find((item) => item.id === targetId)
|
|
225
225
|
const builtin = Boolean(info?.builtin) || ['dafeiyu', 'acha', 'tinglan'].includes(targetId)
|
|
226
|
+
const overlayMode = builtin || String(targetId).startsWith('local:') || info?.source === 'local'
|
|
226
227
|
const section = buildSection(tieba)
|
|
227
|
-
if (
|
|
228
|
+
if (overlayMode) {
|
|
228
229
|
const oldText = readPersonaOverlay(targetId)
|
|
229
230
|
return {
|
|
230
231
|
ok: true,
|
|
@@ -269,6 +270,7 @@ export function createTiebaSync({ ctx, store, logger, getCatalog }) {
|
|
|
269
270
|
const list = await catalog()
|
|
270
271
|
const info = list.find((item) => item.id === targetId)
|
|
271
272
|
const builtin = Boolean(info?.builtin) || ['dafeiyu', 'acha', 'tinglan'].includes(targetId)
|
|
273
|
+
const overlayMode = builtin || String(targetId).startsWith('local:') || info?.source === 'local'
|
|
272
274
|
const section = buildSection(tieba)
|
|
273
275
|
const at = Date.now()
|
|
274
276
|
const stateSync = store.getState().sync || {}
|
|
@@ -277,7 +279,7 @@ export function createTiebaSync({ ctx, store, logger, getCatalog }) {
|
|
|
277
279
|
let reload = null
|
|
278
280
|
let sectionText = section
|
|
279
281
|
|
|
280
|
-
if (
|
|
282
|
+
if (overlayMode) {
|
|
281
283
|
const oldText = readPersonaOverlay(targetId)
|
|
282
284
|
const nextText = applySection(oldText, section).trimEnd() + '\n'
|
|
283
285
|
changed = sansStamp(oldText) !== sansStamp(section)
|
|
@@ -291,7 +293,7 @@ export function createTiebaSync({ ctx, store, logger, getCatalog }) {
|
|
|
291
293
|
fs.writeFileSync(backupPath, oldText)
|
|
292
294
|
}
|
|
293
295
|
overlayWrite(targetId, nextText)
|
|
294
|
-
logger.info(
|
|
296
|
+
logger.info(`已把刷貼吧學到的口氣寫進「${info?.name || targetId}」的覆蓋層`)
|
|
295
297
|
}
|
|
296
298
|
sectionText = nextText
|
|
297
299
|
} else {
|
|
@@ -326,7 +328,7 @@ export function createTiebaSync({ ctx, store, logger, getCatalog }) {
|
|
|
326
328
|
at,
|
|
327
329
|
version: tieba.version,
|
|
328
330
|
targetId,
|
|
329
|
-
mode:
|
|
331
|
+
mode: overlayMode ? 'overlay' : 'persona',
|
|
330
332
|
samples: tieba.samples.length,
|
|
331
333
|
changed,
|
|
332
334
|
backupPath,
|
|
@@ -335,7 +337,7 @@ export function createTiebaSync({ ctx, store, logger, getCatalog }) {
|
|
|
335
337
|
},
|
|
336
338
|
}))
|
|
337
339
|
|
|
338
|
-
if (!
|
|
340
|
+
if (!overlayMode && changed && cfg.reloadPersona !== false) {
|
|
339
341
|
try {
|
|
340
342
|
reload = await reloadPersonaPlugin(ctx)
|
|
341
343
|
if (!reload.ok) logger.warn(`重載大肥魚外掛失敗:${reload.error}`)
|
|
@@ -350,7 +352,7 @@ export function createTiebaSync({ ctx, store, logger, getCatalog }) {
|
|
|
350
352
|
version: tieba.version,
|
|
351
353
|
targetId,
|
|
352
354
|
targetName: info?.name || targetId,
|
|
353
|
-
mode:
|
|
355
|
+
mode: overlayMode ? 'overlay' : 'persona',
|
|
354
356
|
changed,
|
|
355
357
|
samples: tieba.samples.length,
|
|
356
358
|
backupPath,
|