mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-19 11:43:19 +08:00
feat(channels): improve Signal config compatibility
This commit is contained in:
@@ -2472,7 +2472,7 @@ export function normalizeMessagingPlatformForm(platform, form = {}) {
|
||||
normalized.allowedUserIds = csvToStringArray(normalized.allowedUserIds)
|
||||
}
|
||||
|
||||
for (const key of ['mediaMaxMb', 'historyLimit', 'dmHistoryLimit', 'textChunkLimit', 'rateLimitPerMinute']) {
|
||||
for (const key of ['mediaMaxMb', 'historyLimit', 'dmHistoryLimit', 'textChunkLimit', 'rateLimitPerMinute', 'httpPort']) {
|
||||
if (!Object.hasOwn(normalized, key)) continue
|
||||
const value = String(normalized[key] || '').trim()
|
||||
if (!value) {
|
||||
@@ -2883,10 +2883,15 @@ export function buildMessagingPlatformFormValues(platform, saved = {}, options =
|
||||
}
|
||||
|
||||
if (storageKey === 'signal') {
|
||||
for (const key of ['account', 'cliPath', 'httpUrl', 'httpHost', 'httpPort']) {
|
||||
for (const key of ['account', 'cliPath', 'httpUrl', 'httpHost', 'httpPort', 'responsePrefix']) {
|
||||
putSecretAwareFormValue(form, saved, key)
|
||||
}
|
||||
putAccessPolicyFormValues(form, saved)
|
||||
putCsvFormValue(form, saved, 'groupAllowFrom')
|
||||
putBoolFormValue(form, saved, 'blockStreaming')
|
||||
for (const key of ['historyLimit', 'dmHistoryLimit', 'textChunkLimit', 'mediaMaxMb']) {
|
||||
if (typeof saved[key] === 'number') form[key] = String(saved[key])
|
||||
}
|
||||
return form
|
||||
}
|
||||
|
||||
@@ -3494,6 +3499,19 @@ function buildOpenClawMessagingPlatformEntry(platform, form, currentSaved = {})
|
||||
if (Array.isArray(form.allowFrom) && form.allowFrom.length) entry.allowFrom = form.allowFrom
|
||||
if (Array.isArray(form.groupAllowFrom) && form.groupAllowFrom.length) entry.groupAllowFrom = form.groupAllowFrom
|
||||
if (typeof form.mediaMaxMb === 'number') entry.mediaMaxMb = form.mediaMaxMb
|
||||
} else if (storageKey === 'signal') {
|
||||
for (const key of ['account', 'cliPath', 'httpUrl', 'httpHost', 'responsePrefix']) {
|
||||
if (form[key]) entry[key] = form[key]
|
||||
}
|
||||
if (typeof form.httpPort === 'number') entry.httpPort = form.httpPort
|
||||
entry.dmPolicy = form.dmPolicy
|
||||
entry.groupPolicy = form.groupPolicy
|
||||
if (Array.isArray(form.allowFrom) && form.allowFrom.length) entry.allowFrom = form.allowFrom
|
||||
if (Array.isArray(form.groupAllowFrom) && form.groupAllowFrom.length) entry.groupAllowFrom = form.groupAllowFrom
|
||||
if (typeof form.blockStreaming === 'boolean') entry.blockStreaming = form.blockStreaming
|
||||
for (const key of ['historyLimit', 'dmHistoryLimit', 'textChunkLimit', 'mediaMaxMb']) {
|
||||
if (typeof form[key] === 'number') entry[key] = form[key]
|
||||
}
|
||||
} else if (storageKey === 'zalouser') {
|
||||
for (const key of ['profile', 'messagePrefix', 'responsePrefix']) {
|
||||
if (form[key]) entry[key] = form[key]
|
||||
|
||||
@@ -552,6 +552,12 @@ fn insert_array_as_csv(form: &mut Map<String, Value>, source: &Value, key: &str)
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_number_as_string(form: &mut Map<String, Value>, source: &Value, key: &str) {
|
||||
if let Some(v) = source.get(key).and_then(|v| v.as_f64()) {
|
||||
form.insert(key.into(), Value::String(v.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_access_policy_form_values(
|
||||
form: &mut Map<String, Value>,
|
||||
source: &Value,
|
||||
@@ -809,6 +815,7 @@ fn normalize_messaging_platform_form(
|
||||
normalize_numeric_form_value(&mut normalized, "dmHistoryLimit");
|
||||
normalize_numeric_form_value(&mut normalized, "textChunkLimit");
|
||||
normalize_numeric_form_value(&mut normalized, "rateLimitPerMinute");
|
||||
normalize_numeric_form_value(&mut normalized, "httpPort");
|
||||
|
||||
for key in [
|
||||
"dangerouslyAllowNameMatching",
|
||||
@@ -1411,8 +1418,19 @@ pub async fn read_platform_config(
|
||||
insert_string_if_present(&mut form, &saved, "cliPath");
|
||||
insert_string_if_present(&mut form, &saved, "httpUrl");
|
||||
insert_string_if_present(&mut form, &saved, "httpHost");
|
||||
insert_string_if_present(&mut form, &saved, "httpPort");
|
||||
insert_number_as_string(&mut form, &saved, "httpPort");
|
||||
insert_string_if_present(&mut form, &saved, "responsePrefix");
|
||||
insert_access_policy_form_values(&mut form, &saved, false, false);
|
||||
insert_array_as_csv(&mut form, &saved, "groupAllowFrom");
|
||||
insert_bool_as_string(&mut form, &saved, "blockStreaming");
|
||||
for key in [
|
||||
"historyLimit",
|
||||
"dmHistoryLimit",
|
||||
"textChunkLimit",
|
||||
"mediaMaxMb",
|
||||
] {
|
||||
insert_number_as_string(&mut form, &saved, key);
|
||||
}
|
||||
}
|
||||
"matrix" => {
|
||||
insert_string_if_present(&mut form, &saved, "homeserver");
|
||||
@@ -2067,7 +2085,12 @@ pub async fn save_messaging_platform(
|
||||
put_string(&mut entry, "cliPath", form_string(form_obj, "cliPath"));
|
||||
put_string(&mut entry, "httpUrl", form_string(form_obj, "httpUrl"));
|
||||
put_string(&mut entry, "httpHost", form_string(form_obj, "httpHost"));
|
||||
put_string(&mut entry, "httpPort", form_string(form_obj, "httpPort"));
|
||||
put_number_from_form(&mut entry, "httpPort", &form_string(form_obj, "httpPort"));
|
||||
put_string(
|
||||
&mut entry,
|
||||
"responsePrefix",
|
||||
form_string(form_obj, "responsePrefix"),
|
||||
);
|
||||
put_string(&mut entry, "dmPolicy", form_string(form_obj, "dmPolicy"));
|
||||
put_string(
|
||||
&mut entry,
|
||||
@@ -2075,6 +2098,16 @@ pub async fn save_messaging_platform(
|
||||
form_string(form_obj, "groupPolicy"),
|
||||
);
|
||||
put_array_from_form_value(&mut entry, "allowFrom", form_obj.get("allowFrom"));
|
||||
put_array_from_form_value(&mut entry, "groupAllowFrom", form_obj.get("groupAllowFrom"));
|
||||
put_bool_value_if_present(&mut entry, "blockStreaming", form_obj.get("blockStreaming"));
|
||||
for key in [
|
||||
"historyLimit",
|
||||
"dmHistoryLimit",
|
||||
"textChunkLimit",
|
||||
"mediaMaxMb",
|
||||
] {
|
||||
put_number_from_form(&mut entry, key, &form_string(form_obj, key));
|
||||
}
|
||||
preserve_messaging_credential_refs(&mut entry, form_obj, ¤t_saved);
|
||||
merge_channel_entry_for_account(
|
||||
channels_map,
|
||||
|
||||
@@ -228,6 +228,8 @@ export default {
|
||||
signalCliPathLabel: _('signal-cli 路径', 'signal-cli Path', 'signal-cli 路徑'),
|
||||
signalCliPathPh: _('可选,默认从 PATH 查找', 'Optional, defaults to PATH lookup', '可選,預設從 PATH 尋找'),
|
||||
signalAllowFromPh: _('可选,逗号分隔', 'Optional, comma-separated', '可選,逗號分隔'),
|
||||
signalGroupAllowFromPh: _('可选,逗号分隔群组 ID', 'Optional, comma-separated group IDs', '可選,逗號分隔群組 ID'),
|
||||
signalBlockStreaming: _('阻止流式分块', 'Block streaming chunks', '阻止串流分塊'),
|
||||
matrixDesc: _('接入 Matrix 协议(Element 等客户端)', 'Connect via Matrix protocol (Element and other clients)', '接入 Matrix 协議(Element 等用戶端)', 'Matrix プロトコルに接続'),
|
||||
matrixGuide1: _('在 Matrix 服务器上注册 Bot 账号', 'Register a Bot account on a Matrix server', '在 Matrix 伺服器上註冊 Bot 账號'),
|
||||
matrixGuide2: _('获取 <strong>Access Token</strong>(或使用用户名密码)', 'Get an <strong>Access Token</strong> (or use username & password)', '取得 <strong>Access Token</strong>(或使用使用者名密碼)'),
|
||||
|
||||
@@ -461,6 +461,13 @@ const PLATFORM_REGISTRY = {
|
||||
{ key: 'dmPolicy', label: t('channels.dmPolicy'), type: 'select', options: DM_POLICY_OPTIONS, required: false },
|
||||
{ key: 'groupPolicy', label: t('channels.groupPolicy'), type: 'select', options: GROUP_POLICY_OPTIONS(t('channels.groupAllGroups')), required: false },
|
||||
{ key: 'allowFrom', label: 'Allow From', placeholder: t('channels.signalAllowFromPh'), required: false },
|
||||
{ key: 'groupAllowFrom', label: 'Group Allow From', placeholder: t('channels.signalGroupAllowFromPh'), required: false, hint: t('channels.groupAllowFromHint') },
|
||||
{ key: 'historyLimit', label: 'History Limit', placeholder: '80', required: false },
|
||||
{ key: 'dmHistoryLimit', label: 'DM History Limit', placeholder: '20', required: false },
|
||||
{ key: 'textChunkLimit', label: 'Text Chunk Limit', placeholder: '1800', required: false },
|
||||
{ key: 'mediaMaxMb', label: 'Media Max MB', placeholder: '25', required: false },
|
||||
{ key: 'blockStreaming', label: t('channels.signalBlockStreaming'), type: 'select', options: BOOLEAN_OPTIONS, required: false },
|
||||
{ key: 'responsePrefix', label: 'Response Prefix', placeholder: t('channels.optionalEg', { example: '[Signal]' }), required: false },
|
||||
],
|
||||
configKey: 'signal',
|
||||
},
|
||||
@@ -650,7 +657,7 @@ function applyRouteIntent(page, state) {
|
||||
// ── 已配置平台渲染 ──
|
||||
|
||||
// ── 多账号支持的平台:与 OpenClaw 的 accounts/defaultAccount 配置模型保持一致 ──
|
||||
const MULTI_INSTANCE_PLATFORMS = ['telegram', 'discord', 'slack', 'feishu', 'dingtalk', 'dingtalk-connector', 'qqbot', 'zalo', 'zalouser', 'line', 'mattermost', 'synology-chat', 'googlechat']
|
||||
const MULTI_INSTANCE_PLATFORMS = ['telegram', 'discord', 'slack', 'feishu', 'dingtalk', 'dingtalk-connector', 'qqbot', 'zalo', 'zalouser', 'line', 'mattermost', 'synology-chat', 'googlechat', 'signal']
|
||||
|
||||
function supportsMessagingMultiAccount(pid) {
|
||||
return MULTI_INSTANCE_PLATFORMS.includes(pid)
|
||||
|
||||
@@ -832,14 +832,16 @@
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 900;
|
||||
transform: translateX(-100%);
|
||||
transform: translate3d(-100%, 0, 0) !important;
|
||||
transition: transform 250ms cubic-bezier(.22,1,.36,1);
|
||||
box-shadow: none;
|
||||
width: 260px;
|
||||
pointer-events: none;
|
||||
}
|
||||
#sidebar.sidebar-open {
|
||||
transform: translateX(0);
|
||||
transform: translate3d(0, 0, 0) !important;
|
||||
box-shadow: 4px 0 24px rgba(0,0,0,.15);
|
||||
pointer-events: auto;
|
||||
}
|
||||
#main-col {
|
||||
width: 100vw;
|
||||
|
||||
@@ -49,6 +49,69 @@ test('渠道保存不会向不支持顶层 requireMention 的平台写入非法
|
||||
assert.equal(Object.hasOwn(form, 'requireMention'), false)
|
||||
})
|
||||
|
||||
test('Signal 渠道保存会保留多账号和上游运行字段', () => {
|
||||
const cfg = { channels: {} }
|
||||
|
||||
mergeOpenClawMessagingPlatformConfig(cfg, {
|
||||
platform: 'signal',
|
||||
accountId: 'phone-a',
|
||||
form: {
|
||||
account: '+15551234567',
|
||||
cliPath: 'signal-cli',
|
||||
httpUrl: 'http://127.0.0.1:8080',
|
||||
dmPolicy: 'allowlist',
|
||||
allowFrom: '+15550000001',
|
||||
groupPolicy: 'allowlist',
|
||||
groupAllowFrom: 'group-1, group-2',
|
||||
mediaMaxMb: '25',
|
||||
historyLimit: '80',
|
||||
dmHistoryLimit: '20',
|
||||
textChunkLimit: '1800',
|
||||
blockStreaming: 'true',
|
||||
responsePrefix: '[Signal]',
|
||||
},
|
||||
})
|
||||
|
||||
const root = cfg.channels.signal
|
||||
const account = root.accounts['phone-a']
|
||||
assert.equal(root.defaultAccount, 'phone-a')
|
||||
assert.equal(account.account, '+15551234567')
|
||||
assert.equal(account.cliPath, 'signal-cli')
|
||||
assert.equal(account.httpUrl, 'http://127.0.0.1:8080')
|
||||
assert.equal(account.dmPolicy, 'allowlist')
|
||||
assert.deepEqual(account.allowFrom, ['+15550000001'])
|
||||
assert.equal(account.groupPolicy, 'allowlist')
|
||||
assert.deepEqual(account.groupAllowFrom, ['group-1', 'group-2'])
|
||||
assert.equal(account.mediaMaxMb, 25)
|
||||
assert.equal(account.historyLimit, 80)
|
||||
assert.equal(account.dmHistoryLimit, 20)
|
||||
assert.equal(account.textChunkLimit, 1800)
|
||||
assert.equal(account.blockStreaming, true)
|
||||
assert.equal(account.responsePrefix, '[Signal]')
|
||||
})
|
||||
|
||||
test('Signal 渠道读取会回显群组和运行字段', () => {
|
||||
const values = buildMessagingPlatformFormValues('signal', {
|
||||
account: '+15551234567',
|
||||
groupAllowFrom: ['group-1', 'group-2'],
|
||||
mediaMaxMb: 25,
|
||||
historyLimit: 80,
|
||||
dmHistoryLimit: 20,
|
||||
textChunkLimit: 1800,
|
||||
blockStreaming: true,
|
||||
responsePrefix: '[Signal]',
|
||||
})
|
||||
|
||||
assert.equal(values.account, '+15551234567')
|
||||
assert.equal(values.groupAllowFrom, 'group-1, group-2')
|
||||
assert.equal(values.mediaMaxMb, '25')
|
||||
assert.equal(values.historyLimit, '80')
|
||||
assert.equal(values.dmHistoryLimit, '20')
|
||||
assert.equal(values.textChunkLimit, '1800')
|
||||
assert.equal(values.blockStreaming, 'true')
|
||||
assert.equal(values.responsePrefix, '[Signal]')
|
||||
})
|
||||
|
||||
test('渠道保存会为飞书补齐新版内核要求的默认字段', () => {
|
||||
const form = normalizeMessagingPlatformForm('feishu', {
|
||||
appId: 'cli_a',
|
||||
|
||||
Reference in New Issue
Block a user