feat: support wecom (#372)

Co-authored-by: DigHuang <114602213+DigHuang@users.noreply.github.com>
This commit is contained in:
paisley
2026-03-10 12:06:37 +08:00
committed by GitHub
Unverified
parent 905ce02b0b
commit b86f47171b
13 changed files with 326 additions and 54 deletions

View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.477 2 2 5.955 2 10.834c0 2.508 1.134 4.776 2.96 6.376.082.907-.375 2.112-1.077 3.018a10.05 10.05 0 0 0 3.32-.821c1.472.583 3.099.914 4.814.914 5.523 0 10-3.955 10-8.834S17.523 2 12 2z"/>
<path d="M7.5 10l2 3.5 2.5-3.5 2.5 3.5 2-3.5"/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -185,6 +185,25 @@
"Note: Not mentioned in current OpenClaw docs, but you MUST add 'contact:contact.base:readonly' **Application Permission** in Permission Management"
]
},
"wecom": {
"description": "Connect WeCom Bot via plugin",
"docsUrl": "https://open.work.weixin.qq.com/help2/pc/cat?doc_id=21657",
"fields": {
"botId": {
"label": "Bot ID",
"placeholder": "ww_xxxxxx"
},
"secret": {
"label": "App Secret",
"placeholder": "Your WeCom Bot secret"
}
},
"instructions": [
"Create an application in WeCom Admin Console to get configuration info",
"Ensure receive message server config is enabled",
"Enter your Bot ID (or Corp ID) and Secret to establish connection"
]
},
"imessage": {
"description": "Connect iMessage via BlueBubbles (macOS)",
"docsUrl": "https://docs.openclaw.ai/channels/bluebubbles",
@@ -299,4 +318,4 @@
}
},
"viewDocs": "View Documentation"
}
}

View File

@@ -185,6 +185,25 @@
"注意:現在のドキュメントには記載されていませんが、権限管理で 'contact:contact.base:readonly' **アプリケーション権限** を必ず追加してください"
]
},
"wecom": {
"description": "プラグイン経由で WeCom Bot (企業微信) に接続します",
"docsUrl": "https://open.work.weixin.qq.com/help2/pc/cat?doc_id=21657",
"fields": {
"botId": {
"label": "ボット ID",
"placeholder": "ww_xxxxxx"
},
"secret": {
"label": "アプリシークレット",
"placeholder": "WeCom Bot のシークレット"
}
},
"instructions": [
"WeCom 管理コンソールでアプリケーションを作成し、設定情報を取得します",
"メッセージ受信サーバー設定が有効になっていることを確認します",
"ボット ID (または 企業 ID) とシークレットを入力して接続を確立します"
]
},
"imessage": {
"description": "BlueBubbles (macOS) 経由で iMessage に接続します",
"fields": {
@@ -299,4 +318,4 @@
}
},
"viewDocs": "ドキュメントを表示"
}
}

View File

@@ -185,6 +185,25 @@
"注意当前OpenClaw文档中未提及但请务必在权限管理中添加 contact:contact.base:readonly **应用权限**,否则无法正常使用"
]
},
"wecom": {
"description": "通过插件连接企业微信机器人",
"docsUrl": "https://open.work.weixin.qq.com/help2/pc/cat?doc_id=21657",
"fields": {
"botId": {
"label": "机器人 Bot ID",
"placeholder": "ww_xxxxxx"
},
"secret": {
"label": "应用 Secret",
"placeholder": "您的企业微信机器人 Secret"
}
},
"instructions": [
"您可以在企业微信管理后台创建应用并获取配置信息",
"确保已启用接收消息服务器配置",
"填写 Bot ID可选企业 ID 或者直接使用机器人专属 ID及 Secret 即可建立连接"
]
},
"imessage": {
"description": "通过 BlueBubbles (macOS) 连接 iMessage",
"docsUrl": "https://docs.openclaw.ai/zh-CN/channels/bluebubbles",
@@ -299,4 +318,4 @@
}
},
"viewDocs": "查看文档"
}
}

View File

@@ -50,6 +50,7 @@ import discordIcon from '@/assets/channels/discord.svg';
import whatsappIcon from '@/assets/channels/whatsapp.svg';
import dingtalkIcon from '@/assets/channels/dingtalk.svg';
import feishuIcon from '@/assets/channels/feishu.svg';
import wecomIcon from '@/assets/channels/wecom.svg';
export function Channels() {
const { t } = useTranslation('channels');
@@ -112,7 +113,7 @@ export function Channels() {
return (
<div className="flex flex-col -m-6 dark:bg-background h-[calc(100vh-2.5rem)] overflow-hidden">
<div className="w-full max-w-5xl mx-auto flex flex-col h-full p-10 pt-16">
{/* Header */}
<div className="flex flex-col md:flex-row md:items-start justify-between mb-12 shrink-0 gap-4">
<div>
@@ -123,11 +124,14 @@ export function Channels() {
{t('subtitle', 'Manage your messaging channels and connections')}
</p>
</div>
<div className="flex items-center gap-3 md:mt-2">
<Button
variant="outline"
onClick={fetchChannels}
onClick={() => {
void fetchChannels();
void fetchConfiguredTypes();
}}
className="h-9 text-[13px] font-medium rounded-full px-4 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 shadow-none text-foreground/80 hover:text-foreground transition-colors"
>
<RefreshCw className="h-3.5 w-3.5 mr-2" />
@@ -135,10 +139,9 @@ export function Channels() {
</Button>
</div>
</div>
{/* Content Area */}
<div className="flex-1 overflow-y-auto pr-2 pb-10 min-h-0 -mr-2">
{/* Gateway Warning */}
{gatewayStatus.state !== 'running' && (
<div className="mb-8 p-4 rounded-xl border border-yellow-500/50 bg-yellow-500/10 flex items-center gap-3">
@@ -182,15 +185,15 @@ export function Channels() {
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
Supported Channels
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
{displayedChannelTypes.map((type) => {
const meta = CHANNEL_META[type];
const isConfigured = channels.some(c => c.type === type) || configuredTypes.includes(type);
// Hide already configured channels from "Supported Channels" section
if (isConfigured) return null;
return (
<button
key={type}
@@ -223,7 +226,7 @@ export function Channels() {
})}
</div>
</div>
</div>
</div>
@@ -280,6 +283,8 @@ function ChannelLogo({ type }: { type: ChannelType }) {
return <img src={dingtalkIcon} alt="DingTalk" className="w-[22px] h-[22px] dark:invert" />;
case 'feishu':
return <img src={feishuIcon} alt="Feishu" className="w-[22px] h-[22px] dark:invert" />;
case 'wecom':
return <img src={wecomIcon} alt="WeCom" className="w-[22px] h-[22px] dark:invert" />;
default:
return <span className="text-[22px]">{CHANNEL_ICONS[type] || '💬'}</span>;
}
@@ -310,18 +315,18 @@ function ChannelCard({ channel, onDelete }: ChannelCardProps) {
{t('pluginBadge', 'Plugin')}
</Badge>
)}
<div
<div
className={cn(
"w-2 h-2 rounded-full shrink-0",
channel.status === 'connected' ? "bg-green-500" :
channel.status === 'connecting' ? "bg-yellow-500 animate-pulse" :
channel.status === 'error' ? "bg-destructive" :
"bg-muted-foreground"
)}
channel.status === 'connecting' ? "bg-yellow-500 animate-pulse" :
channel.status === 'error' ? "bg-destructive" :
"bg-muted-foreground"
)}
title={channel.status}
/>
</div>
<Button
variant="ghost"
size="icon"
@@ -334,7 +339,7 @@ function ChannelCard({ channel, onDelete }: ChannelCardProps) {
<Trash2 className="h-4 w-4" />
</Button>
</div>
{channel.error ? (
<p className="text-[13.5px] text-destructive line-clamp-2 leading-[1.5]">
{channel.error}

View File

@@ -13,6 +13,7 @@ export type ChannelType =
| 'discord'
| 'signal'
| 'feishu'
| 'wecom'
| 'imessage'
| 'matrix'
| 'line'
@@ -84,6 +85,7 @@ export const CHANNEL_ICONS: Record<ChannelType, string> = {
discord: '🎮',
signal: '🔒',
feishu: '🐦',
wecom: '💼',
imessage: '💬',
matrix: '🔗',
line: '🟢',
@@ -102,6 +104,7 @@ export const CHANNEL_NAMES: Record<ChannelType, string> = {
discord: 'Discord',
signal: 'Signal',
feishu: 'Feishu / Lark',
wecom: 'WeCom',
imessage: 'iMessage',
matrix: 'Matrix',
line: 'LINE',
@@ -166,6 +169,36 @@ export const CHANNEL_META: Record<ChannelType, ChannelMeta> = {
],
isPlugin: true,
},
wecom: {
id: 'wecom',
name: 'WeCom',
icon: '💼',
description: 'channels:meta.wecom.description',
connectionType: 'token',
docsUrl: 'channels:meta.wecom.docsUrl',
configFields: [
{
key: 'botId',
label: 'channels:meta.wecom.fields.botId.label',
type: 'text',
placeholder: 'channels:meta.wecom.fields.botId.placeholder',
required: true,
},
{
key: 'secret',
label: 'channels:meta.wecom.fields.secret.label',
type: 'password',
placeholder: 'channels:meta.wecom.fields.secret.placeholder',
required: true,
},
],
instructions: [
'channels:meta.wecom.instructions.0',
'channels:meta.wecom.instructions.1',
'channels:meta.wecom.instructions.2',
],
isPlugin: true,
},
telegram: {
id: 'telegram',
name: 'Telegram',
@@ -496,7 +529,7 @@ export const CHANNEL_META: Record<ChannelType, ChannelMeta> = {
* Get primary supported channels (non-plugin, commonly used)
*/
export function getPrimaryChannels(): ChannelType[] {
return ['telegram', 'discord', 'whatsapp', 'dingtalk', 'feishu'];
return ['telegram', 'discord', 'whatsapp', 'dingtalk', 'feishu', 'wecom'];
}
/**