fix(channels): complete SendMessage migration after rebase

This commit is contained in:
Chummy 2026-02-17 23:25:52 +08:00
parent dbebd48dfe
commit cd0dd13476
7 changed files with 57 additions and 67 deletions

View file

@ -250,7 +250,10 @@ async fn process_channel_message(ctx: Arc<ChannelRuntimeContext>, msg: traits::C
);
if let Some(channel) = target_channel.as_ref() {
let _ = channel
.send(&SendMessage::new(format!("⚠️ Error: {e}"), &msg.reply_target))
.send(&SendMessage::new(
format!("⚠️ Error: {e}"),
&msg.reply_target,
))
.await;
}
}

View file

@ -1,4 +1,4 @@
use super::traits::{Channel, ChannelMessage};
use super::traits::{Channel, ChannelMessage, SendMessage};
use async_trait::async_trait;
use futures_util::{SinkExt, StreamExt};
use serde_json::json;
@ -162,25 +162,28 @@ impl Channel for QQChannel {
"qq"
}
async fn send(&self, message: &str, recipient: &str) -> anyhow::Result<()> {
async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
let token = self.get_token().await?;
// Determine if this is a group or private message based on recipient format
// Format: "user:{openid}" or "group:{group_openid}"
let (url, body) = if let Some(group_id) = recipient.strip_prefix("group:") {
let (url, body) = if let Some(group_id) = message.recipient.strip_prefix("group:") {
(
format!("{QQ_API_BASE}/v2/groups/{group_id}/messages"),
json!({
"content": message,
"content": &message.content,
"msg_type": 0,
}),
)
} else {
let user_id = recipient.strip_prefix("user:").unwrap_or(recipient);
let user_id = message
.recipient
.strip_prefix("user:")
.unwrap_or(&message.recipient);
(
format!("{QQ_API_BASE}/v2/users/{user_id}/messages"),
json!({
"content": message,
"content": &message.content,
"msg_type": 0,
}),
)

View file

@ -1,4 +1,4 @@
use crate::channels::traits::{Channel, ChannelMessage};
use crate::channels::traits::{Channel, ChannelMessage, SendMessage};
use async_trait::async_trait;
use futures_util::StreamExt;
use reqwest::Client;
@ -269,17 +269,17 @@ impl Channel for SignalChannel {
"signal"
}
async fn send(&self, message: &str, recipient: &str) -> anyhow::Result<()> {
let params = match Self::parse_recipient_target(recipient) {
async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
let params = match Self::parse_recipient_target(&message.recipient) {
RecipientTarget::Direct(number) => serde_json::json!({
"recipient": [number],
"message": message,
"account": self.account,
"message": &message.content,
"account": &self.account,
}),
RecipientTarget::Group(group_id) => serde_json::json!({
"groupId": group_id,
"message": message,
"account": self.account,
"message": &message.content,
"account": &self.account,
}),
};
@ -423,11 +423,11 @@ impl Channel for SignalChannel {
let params = match Self::parse_recipient_target(recipient) {
RecipientTarget::Direct(number) => serde_json::json!({
"recipient": [number],
"account": self.account,
"account": &self.account,
}),
RecipientTarget::Group(group_id) => serde_json::json!({
"groupId": group_id,
"account": self.account,
"account": &self.account,
}),
};
self.rpc_request("sendTyping", params).await?;

View file

@ -380,10 +380,10 @@ impl TelegramChannel {
match self.persist_allowed_identity(&identity).await {
Ok(()) => {
let _ = self
.send(
.send(&SendMessage::new(
"✅ Telegram account bound successfully. You can talk to ZeroClaw now.",
&chat_id,
)
))
.await;
tracing::info!(
"Telegram: paired and allowlisted identity={identity}"
@ -394,45 +394,45 @@ impl TelegramChannel {
"Telegram: failed to persist allowlist after bind: {e}"
);
let _ = self
.send(
.send(&SendMessage::new(
"⚠️ Bound for this runtime, but failed to persist config. Access may be lost after restart; check config file permissions.",
&chat_id,
)
))
.await;
}
}
} else {
let _ = self
.send(
.send(&SendMessage::new(
"❌ Could not identify your Telegram account. Ensure your account has a username or stable user ID, then retry.",
&chat_id,
)
))
.await;
}
}
Ok(None) => {
let _ = self
.send(
.send(&SendMessage::new(
"❌ Invalid binding code. Ask operator for the latest code and retry.",
&chat_id,
)
))
.await;
}
Err(lockout_secs) => {
let _ = self
.send(
&format!("⏳ Too many invalid attempts. Retry in {lockout_secs}s."),
.send(&SendMessage::new(
format!("⏳ Too many invalid attempts. Retry in {lockout_secs}s."),
&chat_id,
)
))
.await;
}
}
} else {
let _ = self
.send(
.send(&SendMessage::new(
" Telegram pairing is not active. Ask operator to update allowlist in config.toml.",
&chat_id,
)
))
.await;
}
return;
@ -456,23 +456,20 @@ Allowlist Telegram username (without '@') or numeric user ID.",
.unwrap_or_else(|| "YOUR_TELEGRAM_ID".to_string());
let _ = self
.send(
&format!(
"🔐 This bot requires operator approval.\n\n\
Copy this command to operator terminal:\n\
`zeroclaw channel bind-telegram {suggested_identity}`\n\n\
After operator runs it, send your message again."
.send(&SendMessage::new(
format!(
"🔐 This bot requires operator approval.\n\nCopy this command to operator terminal:\n`zeroclaw channel bind-telegram {suggested_identity}`\n\nAfter operator runs it, send your message again."
),
&chat_id,
)
))
.await;
if self.pairing_code_active() {
let _ = self
.send(
.send(&SendMessage::new(
" If operator provides a one-time pairing code, you can also run `/bind <code>`.",
&chat_id,
)
))
.await;
}
}
@ -1066,7 +1063,8 @@ impl Channel for TelegramChannel {
}
if let Some(attachment) = parse_path_only_attachment(&message.content) {
self.send_attachment(&message.recipient, &attachment).await?;
self.send_attachment(&message.recipient, &attachment)
.await?;
return Ok(());
}
@ -1369,7 +1367,7 @@ mod tests {
"username": "alice"
},
"chat": {
"id": -100200300
"id": -100_200_300
}
}
});

View file

@ -12,7 +12,7 @@ pub struct ChannelMessage {
}
/// Message to send through a channel
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct SendMessage {
pub content: String,
pub recipient: String,
@ -43,26 +43,6 @@ impl SendMessage {
}
}
impl From<&str> for SendMessage {
fn from(content: &str) -> Self {
Self {
content: content.to_string(),
recipient: String::new(),
subject: None,
}
}
}
impl From<(String, String)> for SendMessage {
fn from(value: (String, String)) -> Self {
Self {
content: value.0,
recipient: value.1,
subject: None,
}
}
}
/// Core channel trait — implement for any messaging platform
#[async_trait]
pub trait Channel: Send + Sync {
@ -152,7 +132,10 @@ mod tests {
assert!(channel.health_check().await);
assert!(channel.start_typing("bob").await.is_ok());
assert!(channel.stop_typing("bob").await.is_ok());
assert!(channel.send(&SendMessage::new("hello", "bob")).await.is_ok());
assert!(channel
.send(&SendMessage::new("hello", "bob"))
.await
.is_ok());
}
#[tokio::test]

View file

@ -1,4 +1,4 @@
use crate::channels::{Channel, DiscordChannel, SlackChannel, TelegramChannel};
use crate::channels::{Channel, DiscordChannel, SendMessage, SlackChannel, TelegramChannel};
use crate::config::Config;
use crate::cron::{
due_jobs, next_run_for_schedule, record_last_run, record_run, remove_job, reschedule_after_run,
@ -232,7 +232,7 @@ async fn deliver_if_configured(config: &Config, job: &CronJob, output: &str) ->
.as_ref()
.ok_or_else(|| anyhow::anyhow!("telegram channel not configured"))?;
let channel = TelegramChannel::new(tg.bot_token.clone(), tg.allowed_users.clone());
channel.send(output, target).await?;
channel.send(&SendMessage::new(output, target)).await?;
}
"discord" => {
let dc = config
@ -247,7 +247,7 @@ async fn deliver_if_configured(config: &Config, job: &CronJob, output: &str) ->
dc.listen_to_bots,
dc.mention_only,
);
channel.send(output, target).await?;
channel.send(&SendMessage::new(output, target)).await?;
}
"slack" => {
let sl = config
@ -260,7 +260,7 @@ async fn deliver_if_configured(config: &Config, job: &CronJob, output: &str) ->
sl.channel_id.clone(),
sl.allowed_users.clone(),
);
channel.send(output, target).await?;
channel.send(&SendMessage::new(output, target)).await?;
}
other => anyhow::bail!("unsupported delivery channel: {other}"),
}

View file

@ -704,7 +704,10 @@ async fn handle_whatsapp_message(
{
Ok(response) => {
// Send reply via WhatsApp
if let Err(e) = wa.send(&SendMessage::new(response, &msg.reply_target)).await {
if let Err(e) = wa
.send(&SendMessage::new(response, &msg.reply_target))
.await
{
tracing::error!("Failed to send WhatsApp reply: {e}");
}
}