Skip to main content

clawft_types/config/
channels.rs

1//! Channel configuration types.
2//!
3//! Configuration for all supported chat channels: Telegram, Slack, Discord,
4//! WhatsApp, Feishu, DingTalk, Mochat, Email, and QQ.
5
6use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::secret::SecretString;
11
12/// Configuration for all chat channels.
13///
14/// The `extra` field captures unknown channel plugins via `#[serde(flatten)]`.
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16pub struct ChannelsConfig {
17    /// Telegram bot configuration.
18    #[serde(default)]
19    pub telegram: TelegramConfig,
20
21    /// Slack configuration.
22    #[serde(default)]
23    pub slack: SlackConfig,
24
25    /// Discord bot configuration.
26    #[serde(default)]
27    pub discord: DiscordConfig,
28
29    /// WhatsApp bridge configuration.
30    #[serde(default)]
31    pub whatsapp: WhatsAppConfig,
32
33    /// Feishu / Lark configuration.
34    #[serde(default)]
35    pub feishu: FeishuConfig,
36
37    /// DingTalk configuration.
38    #[serde(default)]
39    pub dingtalk: DingTalkConfig,
40
41    /// Mochat configuration.
42    #[serde(default)]
43    pub mochat: MochatConfig,
44
45    /// Email (IMAP + SMTP) configuration.
46    #[serde(default)]
47    pub email: EmailConfig,
48
49    /// QQ bot configuration.
50    #[serde(default)]
51    pub qq: QQConfig,
52
53    /// Unknown channel plugins (forward compatibility).
54    #[serde(flatten)]
55    pub extra: HashMap<String, serde_json::Value>,
56}
57
58/// Telegram channel configuration.
59#[derive(Debug, Clone, Serialize, Deserialize, Default)]
60pub struct TelegramConfig {
61    /// Whether this channel is enabled.
62    #[serde(default)]
63    pub enabled: bool,
64
65    /// Bot token from `@BotFather`.
66    #[serde(default)]
67    pub token: SecretString,
68
69    /// Environment variable name that holds the bot token (e.g. `"TELEGRAM_BOT_TOKEN"`).
70    /// When set, the env var is used if `token` is empty.
71    #[serde(default, alias = "tokenEnv")]
72    pub token_env: Option<String>,
73
74    /// Allowed user IDs or usernames. Empty = allow all.
75    #[serde(default, alias = "allowFrom")]
76    pub allow_from: Vec<String>,
77
78    /// HTTP/SOCKS5 proxy URL.
79    #[serde(default)]
80    pub proxy: Option<String>,
81}
82
83/// Slack channel configuration.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SlackConfig {
86    /// Whether this channel is enabled.
87    #[serde(default)]
88    pub enabled: bool,
89
90    /// Connection mode (currently only `"socket"` is supported).
91    #[serde(default = "default_slack_mode")]
92    pub mode: String,
93
94    /// Webhook path for event subscriptions.
95    #[serde(default = "default_webhook_path", alias = "webhookPath")]
96    pub webhook_path: String,
97
98    /// Bot token (`xoxb-...`).
99    #[serde(default, alias = "botToken")]
100    pub bot_token: SecretString,
101
102    /// Environment variable name for the bot token (e.g. `"SLACK_BOT_TOKEN"`).
103    /// Used when `bot_token` is empty.
104    #[serde(default, alias = "botTokenEnv")]
105    pub bot_token_env: Option<String>,
106
107    /// App-level token (`xapp-...`).
108    #[serde(default, alias = "appToken")]
109    pub app_token: SecretString,
110
111    /// Environment variable name for the app token (e.g. `"SLACK_APP_TOKEN"`).
112    /// Used when `app_token` is empty.
113    #[serde(default, alias = "appTokenEnv")]
114    pub app_token_env: Option<String>,
115
116    /// Whether the user token is read-only.
117    #[serde(default = "super::default_true", alias = "userTokenReadOnly")]
118    pub user_token_read_only: bool,
119
120    /// Group message policy: `"mention"`, `"open"`, or `"allowlist"`.
121    #[serde(default = "default_group_policy", alias = "groupPolicy")]
122    pub group_policy: String,
123
124    /// Allowed channel IDs when `group_policy` is `"allowlist"`.
125    #[serde(default, alias = "groupAllowFrom")]
126    pub group_allow_from: Vec<String>,
127
128    /// DM-specific policy.
129    #[serde(default)]
130    pub dm: SlackDMConfig,
131}
132
133fn default_slack_mode() -> String {
134    "socket".into()
135}
136fn default_webhook_path() -> String {
137    "/slack/events".into()
138}
139fn default_group_policy() -> String {
140    "mention".into()
141}
142
143impl Default for SlackConfig {
144    fn default() -> Self {
145        Self {
146            enabled: false,
147            mode: default_slack_mode(),
148            webhook_path: default_webhook_path(),
149            bot_token: SecretString::default(),
150            bot_token_env: None,
151            app_token: SecretString::default(),
152            app_token_env: None,
153            user_token_read_only: true,
154            group_policy: default_group_policy(),
155            group_allow_from: Vec::new(),
156            dm: SlackDMConfig::default(),
157        }
158    }
159}
160
161/// Slack DM policy configuration.
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct SlackDMConfig {
164    /// Whether DMs are enabled.
165    #[serde(default = "super::default_true")]
166    pub enabled: bool,
167
168    /// DM policy: `"open"` or `"allowlist"`.
169    #[serde(default = "default_dm_policy")]
170    pub policy: String,
171
172    /// Allowed Slack user IDs when policy is `"allowlist"`.
173    #[serde(default, alias = "allowFrom")]
174    pub allow_from: Vec<String>,
175}
176
177fn default_dm_policy() -> String {
178    "open".into()
179}
180
181impl Default for SlackDMConfig {
182    fn default() -> Self {
183        Self {
184            enabled: true,
185            policy: default_dm_policy(),
186            allow_from: Vec::new(),
187        }
188    }
189}
190
191/// Discord channel configuration.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct DiscordConfig {
194    /// Whether this channel is enabled.
195    #[serde(default)]
196    pub enabled: bool,
197
198    /// Bot token from Discord Developer Portal.
199    #[serde(default)]
200    pub token: SecretString,
201
202    /// Environment variable name that holds the bot token (e.g. `"DISCORD_BOT_TOKEN"`).
203    /// When set, the env var is used if `token` is empty.
204    #[serde(default, alias = "tokenEnv")]
205    pub token_env: Option<String>,
206
207    /// Allowed user IDs. Empty = allow all.
208    #[serde(default, alias = "allowFrom")]
209    pub allow_from: Vec<String>,
210
211    /// Gateway WebSocket URL.
212    #[serde(default = "default_discord_gateway_url", alias = "gatewayUrl")]
213    pub gateway_url: String,
214
215    /// Gateway intents bitmask.
216    #[serde(default = "default_discord_intents")]
217    pub intents: u32,
218}
219
220fn default_discord_gateway_url() -> String {
221    "wss://gateway.discord.gg/?v=10&encoding=json".into()
222}
223fn default_discord_intents() -> u32 {
224    37377 // GUILDS + GUILD_MESSAGES + DIRECT_MESSAGES + MESSAGE_CONTENT
225}
226
227impl Default for DiscordConfig {
228    fn default() -> Self {
229        Self {
230            enabled: false,
231            token: SecretString::default(),
232            token_env: None,
233            allow_from: Vec::new(),
234            gateway_url: default_discord_gateway_url(),
235            intents: default_discord_intents(),
236        }
237    }
238}
239
240/// WhatsApp bridge configuration.
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct WhatsAppConfig {
243    /// Whether this channel is enabled.
244    #[serde(default)]
245    pub enabled: bool,
246
247    /// WebSocket bridge URL.
248    #[serde(default = "default_whatsapp_bridge_url", alias = "bridgeUrl")]
249    pub bridge_url: String,
250
251    /// Shared token for bridge authentication.
252    #[serde(default, alias = "bridgeToken")]
253    pub bridge_token: SecretString,
254
255    /// Allowed phone numbers. Empty = allow all.
256    #[serde(default, alias = "allowFrom")]
257    pub allow_from: Vec<String>,
258}
259
260fn default_whatsapp_bridge_url() -> String {
261    "ws://localhost:3001".into()
262}
263
264impl Default for WhatsAppConfig {
265    fn default() -> Self {
266        Self {
267            enabled: false,
268            bridge_url: default_whatsapp_bridge_url(),
269            bridge_token: SecretString::default(),
270            allow_from: Vec::new(),
271        }
272    }
273}
274
275/// Feishu / Lark channel configuration.
276#[derive(Debug, Clone, Serialize, Deserialize, Default)]
277pub struct FeishuConfig {
278    /// Whether this channel is enabled.
279    #[serde(default)]
280    pub enabled: bool,
281
282    /// App ID from Feishu Open Platform.
283    #[serde(default, alias = "appId")]
284    pub app_id: String,
285
286    /// App Secret from Feishu Open Platform.
287    #[serde(default, alias = "appSecret")]
288    pub app_secret: SecretString,
289
290    /// Encrypt Key for event subscription.
291    #[serde(default, alias = "encryptKey")]
292    pub encrypt_key: String,
293
294    /// Verification Token for event subscription.
295    #[serde(default, alias = "verificationToken")]
296    pub verification_token: SecretString,
297
298    /// Allowed user open_ids. Empty = allow all.
299    #[serde(default, alias = "allowFrom")]
300    pub allow_from: Vec<String>,
301}
302
303/// DingTalk channel configuration.
304#[derive(Debug, Clone, Serialize, Deserialize, Default)]
305pub struct DingTalkConfig {
306    /// Whether this channel is enabled.
307    #[serde(default)]
308    pub enabled: bool,
309
310    /// AppKey.
311    #[serde(default, alias = "clientId")]
312    pub client_id: String,
313
314    /// AppSecret.
315    #[serde(default, alias = "clientSecret")]
316    pub client_secret: SecretString,
317
318    /// Allowed staff IDs. Empty = allow all.
319    #[serde(default, alias = "allowFrom")]
320    pub allow_from: Vec<String>,
321}
322
323/// Mochat mention behavior configuration.
324#[derive(Debug, Clone, Serialize, Deserialize, Default)]
325pub struct MochatMentionConfig {
326    /// Whether mentions are required in group messages.
327    #[serde(default, alias = "requireInGroups")]
328    pub require_in_groups: bool,
329}
330
331/// Mochat per-group mention requirement.
332#[derive(Debug, Clone, Serialize, Deserialize, Default)]
333pub struct MochatGroupRule {
334    /// Whether mentions are required in this group.
335    #[serde(default, alias = "requireMention")]
336    pub require_mention: bool,
337}
338
339/// Mochat channel configuration.
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct MochatConfig {
342    /// Whether this channel is enabled.
343    #[serde(default)]
344    pub enabled: bool,
345
346    /// Mochat API base URL.
347    #[serde(default = "default_mochat_base_url", alias = "baseUrl")]
348    pub base_url: String,
349
350    /// WebSocket URL for real-time messaging.
351    #[serde(default, alias = "socketUrl")]
352    pub socket_url: String,
353
354    /// Socket.IO path.
355    #[serde(default = "default_socket_path", alias = "socketPath")]
356    pub socket_path: String,
357
358    /// Disable msgpack encoding for Socket.IO.
359    #[serde(default, alias = "socketDisableMsgpack")]
360    pub socket_disable_msgpack: bool,
361
362    /// Reconnect delay in milliseconds.
363    #[serde(default = "default_1000", alias = "socketReconnectDelayMs")]
364    pub socket_reconnect_delay_ms: i64,
365
366    /// Maximum reconnect delay in milliseconds.
367    #[serde(default = "default_10000", alias = "socketMaxReconnectDelayMs")]
368    pub socket_max_reconnect_delay_ms: i64,
369
370    /// Connection timeout in milliseconds.
371    #[serde(default = "default_10000", alias = "socketConnectTimeoutMs")]
372    pub socket_connect_timeout_ms: i64,
373
374    /// Room refresh interval in milliseconds.
375    #[serde(default = "default_30000", alias = "refreshIntervalMs")]
376    pub refresh_interval_ms: i64,
377
378    /// Watch timeout in milliseconds.
379    #[serde(default = "default_25000", alias = "watchTimeoutMs")]
380    pub watch_timeout_ms: i64,
381
382    /// Maximum messages per watch cycle.
383    #[serde(default = "default_100", alias = "watchLimit")]
384    pub watch_limit: i64,
385
386    /// Retry delay in milliseconds.
387    #[serde(default = "default_500", alias = "retryDelayMs")]
388    pub retry_delay_ms: i64,
389
390    /// Maximum retry attempts (0 = unlimited).
391    #[serde(default, alias = "maxRetryAttempts")]
392    pub max_retry_attempts: i64,
393
394    /// Authentication token.
395    #[serde(default, alias = "clawToken")]
396    pub claw_token: SecretString,
397
398    /// Agent user ID for the bot.
399    #[serde(default, alias = "agentUserId")]
400    pub agent_user_id: String,
401
402    /// Session IDs to monitor.
403    #[serde(default)]
404    pub sessions: Vec<String>,
405
406    /// Panel IDs to monitor.
407    #[serde(default)]
408    pub panels: Vec<String>,
409
410    /// Allowed user IDs. Empty = allow all.
411    #[serde(default, alias = "allowFrom")]
412    pub allow_from: Vec<String>,
413
414    /// Mention behavior configuration.
415    #[serde(default)]
416    pub mention: MochatMentionConfig,
417
418    /// Per-group rules keyed by group ID.
419    #[serde(default)]
420    pub groups: HashMap<String, MochatGroupRule>,
421
422    /// Reply delay mode: `"off"` or `"non-mention"`.
423    #[serde(default = "default_reply_delay_mode", alias = "replyDelayMode")]
424    pub reply_delay_mode: String,
425
426    /// Reply delay in milliseconds.
427    #[serde(default = "default_120000", alias = "replyDelayMs")]
428    pub reply_delay_ms: i64,
429}
430
431fn default_mochat_base_url() -> String {
432    "https://mochat.io".into()
433}
434fn default_socket_path() -> String {
435    "/socket.io".into()
436}
437fn default_reply_delay_mode() -> String {
438    "non-mention".into()
439}
440fn default_1000() -> i64 {
441    1000
442}
443fn default_10000() -> i64 {
444    10000
445}
446fn default_30000() -> i64 {
447    30000
448}
449fn default_25000() -> i64 {
450    25000
451}
452fn default_100() -> i64 {
453    100
454}
455fn default_500() -> i64 {
456    500
457}
458fn default_120000() -> i64 {
459    120000
460}
461
462impl Default for MochatConfig {
463    fn default() -> Self {
464        Self {
465            enabled: false,
466            base_url: default_mochat_base_url(),
467            socket_url: String::new(),
468            socket_path: default_socket_path(),
469            socket_disable_msgpack: false,
470            socket_reconnect_delay_ms: default_1000(),
471            socket_max_reconnect_delay_ms: default_10000(),
472            socket_connect_timeout_ms: default_10000(),
473            refresh_interval_ms: default_30000(),
474            watch_timeout_ms: default_25000(),
475            watch_limit: default_100(),
476            retry_delay_ms: default_500(),
477            max_retry_attempts: 0,
478            claw_token: SecretString::default(),
479            agent_user_id: String::new(),
480            sessions: Vec::new(),
481            panels: Vec::new(),
482            allow_from: Vec::new(),
483            mention: MochatMentionConfig::default(),
484            groups: HashMap::new(),
485            reply_delay_mode: default_reply_delay_mode(),
486            reply_delay_ms: default_120000(),
487        }
488    }
489}
490
491/// Email channel configuration (IMAP inbound + SMTP outbound).
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct EmailConfig {
494    /// Whether this channel is enabled.
495    #[serde(default)]
496    pub enabled: bool,
497
498    /// Explicit owner permission to access mailbox data.
499    #[serde(default, alias = "consentGranted")]
500    pub consent_granted: bool,
501
502    // -- IMAP (receive) --
503    /// IMAP server hostname.
504    #[serde(default, alias = "imapHost")]
505    pub imap_host: String,
506
507    /// IMAP server port.
508    #[serde(default = "default_imap_port", alias = "imapPort")]
509    pub imap_port: u16,
510
511    /// IMAP username.
512    #[serde(default, alias = "imapUsername")]
513    pub imap_username: String,
514
515    /// IMAP password.
516    #[serde(default, alias = "imapPassword")]
517    pub imap_password: SecretString,
518
519    /// IMAP mailbox to watch.
520    #[serde(default = "default_imap_mailbox", alias = "imapMailbox")]
521    pub imap_mailbox: String,
522
523    /// Use SSL for IMAP.
524    #[serde(default = "super::default_true", alias = "imapUseSsl")]
525    pub imap_use_ssl: bool,
526
527    // -- SMTP (send) --
528    /// SMTP server hostname.
529    #[serde(default, alias = "smtpHost")]
530    pub smtp_host: String,
531
532    /// SMTP server port.
533    #[serde(default = "default_smtp_port", alias = "smtpPort")]
534    pub smtp_port: u16,
535
536    /// SMTP username.
537    #[serde(default, alias = "smtpUsername")]
538    pub smtp_username: String,
539
540    /// SMTP password.
541    #[serde(default, alias = "smtpPassword")]
542    pub smtp_password: SecretString,
543
544    /// Use STARTTLS for SMTP.
545    #[serde(default = "super::default_true", alias = "smtpUseTls")]
546    pub smtp_use_tls: bool,
547
548    /// Use implicit SSL for SMTP.
549    #[serde(default, alias = "smtpUseSsl")]
550    pub smtp_use_ssl: bool,
551
552    /// From address for outgoing emails.
553    #[serde(default, alias = "fromAddress")]
554    pub from_address: String,
555
556    // -- Behavior --
557    /// If false, inbound email is read but no automatic reply is sent.
558    #[serde(default = "super::default_true", alias = "autoReplyEnabled")]
559    pub auto_reply_enabled: bool,
560
561    /// Polling interval in seconds.
562    #[serde(default = "default_poll_interval", alias = "pollIntervalSeconds")]
563    pub poll_interval_seconds: u32,
564
565    /// Mark messages as seen after processing.
566    #[serde(default = "super::default_true", alias = "markSeen")]
567    pub mark_seen: bool,
568
569    /// Maximum email body characters to process.
570    #[serde(default = "default_max_body_chars", alias = "maxBodyChars")]
571    pub max_body_chars: u32,
572
573    /// Prefix for reply subjects.
574    #[serde(default = "default_subject_prefix", alias = "subjectPrefix")]
575    pub subject_prefix: String,
576
577    /// Allowed sender email addresses. Empty = allow all.
578    #[serde(default, alias = "allowFrom")]
579    pub allow_from: Vec<String>,
580}
581
582fn default_imap_port() -> u16 {
583    993
584}
585fn default_imap_mailbox() -> String {
586    "INBOX".into()
587}
588fn default_smtp_port() -> u16 {
589    587
590}
591fn default_poll_interval() -> u32 {
592    30
593}
594fn default_max_body_chars() -> u32 {
595    12000
596}
597fn default_subject_prefix() -> String {
598    "Re: ".into()
599}
600
601impl Default for EmailConfig {
602    fn default() -> Self {
603        Self {
604            enabled: false,
605            consent_granted: false,
606            imap_host: String::new(),
607            imap_port: default_imap_port(),
608            imap_username: String::new(),
609            imap_password: SecretString::default(),
610            imap_mailbox: default_imap_mailbox(),
611            imap_use_ssl: true,
612            smtp_host: String::new(),
613            smtp_port: default_smtp_port(),
614            smtp_username: String::new(),
615            smtp_password: SecretString::default(),
616            smtp_use_tls: true,
617            smtp_use_ssl: false,
618            from_address: String::new(),
619            auto_reply_enabled: true,
620            poll_interval_seconds: default_poll_interval(),
621            mark_seen: true,
622            max_body_chars: default_max_body_chars(),
623            subject_prefix: default_subject_prefix(),
624            allow_from: Vec::new(),
625        }
626    }
627}
628
629/// QQ bot configuration.
630#[derive(Debug, Clone, Serialize, Deserialize, Default)]
631pub struct QQConfig {
632    /// Whether this channel is enabled.
633    #[serde(default)]
634    pub enabled: bool,
635
636    /// Bot AppID.
637    #[serde(default, alias = "appId")]
638    pub app_id: String,
639
640    /// Bot AppSecret.
641    #[serde(default)]
642    pub secret: SecretString,
643
644    /// Allowed user openids. Empty = allow all.
645    #[serde(default, alias = "allowFrom")]
646    pub allow_from: Vec<String>,
647}