feat: support wecom (#372)
Co-authored-by: DigHuang <114602213+DigHuang@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
905ce02b0b
commit
b86f47171b
4
src/assets/channels/wecom.svg
Normal file
4
src/assets/channels/wecom.svg
Normal 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 |
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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": "ドキュメントを表示"
|
||||
}
|
||||
}
|
||||
@@ -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": "查看文档"
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user