fix(channels): complete SendMessage migration after rebase
This commit is contained in:
parent
dbebd48dfe
commit
cd0dd13476
7 changed files with 57 additions and 67 deletions
|
|
@ -250,7 +250,10 @@ async fn process_channel_message(ctx: Arc<ChannelRuntimeContext>, msg: traits::C
|
||||||
);
|
);
|
||||||
if let Some(channel) = target_channel.as_ref() {
|
if let Some(channel) = target_channel.as_ref() {
|
||||||
let _ = channel
|
let _ = channel
|
||||||
.send(&SendMessage::new(format!("⚠️ Error: {e}"), &msg.reply_target))
|
.send(&SendMessage::new(
|
||||||
|
format!("⚠️ Error: {e}"),
|
||||||
|
&msg.reply_target,
|
||||||
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::traits::{Channel, ChannelMessage};
|
use super::traits::{Channel, ChannelMessage, SendMessage};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::{SinkExt, StreamExt};
|
use futures_util::{SinkExt, StreamExt};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
@ -162,25 +162,28 @@ impl Channel for QQChannel {
|
||||||
"qq"
|
"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?;
|
let token = self.get_token().await?;
|
||||||
|
|
||||||
// Determine if this is a group or private message based on recipient format
|
// Determine if this is a group or private message based on recipient format
|
||||||
// Format: "user:{openid}" or "group:{group_openid}"
|
// 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"),
|
format!("{QQ_API_BASE}/v2/groups/{group_id}/messages"),
|
||||||
json!({
|
json!({
|
||||||
"content": message,
|
"content": &message.content,
|
||||||
"msg_type": 0,
|
"msg_type": 0,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else {
|
} 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"),
|
format!("{QQ_API_BASE}/v2/users/{user_id}/messages"),
|
||||||
json!({
|
json!({
|
||||||
"content": message,
|
"content": &message.content,
|
||||||
"msg_type": 0,
|
"msg_type": 0,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::channels::traits::{Channel, ChannelMessage};
|
use crate::channels::traits::{Channel, ChannelMessage, SendMessage};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
@ -269,17 +269,17 @@ impl Channel for SignalChannel {
|
||||||
"signal"
|
"signal"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send(&self, message: &str, recipient: &str) -> anyhow::Result<()> {
|
async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
|
||||||
let params = match Self::parse_recipient_target(recipient) {
|
let params = match Self::parse_recipient_target(&message.recipient) {
|
||||||
RecipientTarget::Direct(number) => serde_json::json!({
|
RecipientTarget::Direct(number) => serde_json::json!({
|
||||||
"recipient": [number],
|
"recipient": [number],
|
||||||
"message": message,
|
"message": &message.content,
|
||||||
"account": self.account,
|
"account": &self.account,
|
||||||
}),
|
}),
|
||||||
RecipientTarget::Group(group_id) => serde_json::json!({
|
RecipientTarget::Group(group_id) => serde_json::json!({
|
||||||
"groupId": group_id,
|
"groupId": group_id,
|
||||||
"message": message,
|
"message": &message.content,
|
||||||
"account": self.account,
|
"account": &self.account,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -423,11 +423,11 @@ impl Channel for SignalChannel {
|
||||||
let params = match Self::parse_recipient_target(recipient) {
|
let params = match Self::parse_recipient_target(recipient) {
|
||||||
RecipientTarget::Direct(number) => serde_json::json!({
|
RecipientTarget::Direct(number) => serde_json::json!({
|
||||||
"recipient": [number],
|
"recipient": [number],
|
||||||
"account": self.account,
|
"account": &self.account,
|
||||||
}),
|
}),
|
||||||
RecipientTarget::Group(group_id) => serde_json::json!({
|
RecipientTarget::Group(group_id) => serde_json::json!({
|
||||||
"groupId": group_id,
|
"groupId": group_id,
|
||||||
"account": self.account,
|
"account": &self.account,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
self.rpc_request("sendTyping", params).await?;
|
self.rpc_request("sendTyping", params).await?;
|
||||||
|
|
|
||||||
|
|
@ -380,10 +380,10 @@ impl TelegramChannel {
|
||||||
match self.persist_allowed_identity(&identity).await {
|
match self.persist_allowed_identity(&identity).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let _ = self
|
let _ = self
|
||||||
.send(
|
.send(&SendMessage::new(
|
||||||
"✅ Telegram account bound successfully. You can talk to ZeroClaw now.",
|
"✅ Telegram account bound successfully. You can talk to ZeroClaw now.",
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Telegram: paired and allowlisted identity={identity}"
|
"Telegram: paired and allowlisted identity={identity}"
|
||||||
|
|
@ -394,45 +394,45 @@ impl TelegramChannel {
|
||||||
"Telegram: failed to persist allowlist after bind: {e}"
|
"Telegram: failed to persist allowlist after bind: {e}"
|
||||||
);
|
);
|
||||||
let _ = self
|
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.",
|
"⚠️ Bound for this runtime, but failed to persist config. Access may be lost after restart; check config file permissions.",
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = self
|
let _ = self
|
||||||
.send(
|
.send(&SendMessage::new(
|
||||||
"❌ Could not identify your Telegram account. Ensure your account has a username or stable user ID, then retry.",
|
"❌ Could not identify your Telegram account. Ensure your account has a username or stable user ID, then retry.",
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
let _ = self
|
let _ = self
|
||||||
.send(
|
.send(&SendMessage::new(
|
||||||
"❌ Invalid binding code. Ask operator for the latest code and retry.",
|
"❌ Invalid binding code. Ask operator for the latest code and retry.",
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
Err(lockout_secs) => {
|
Err(lockout_secs) => {
|
||||||
let _ = self
|
let _ = self
|
||||||
.send(
|
.send(&SendMessage::new(
|
||||||
&format!("⏳ Too many invalid attempts. Retry in {lockout_secs}s."),
|
format!("⏳ Too many invalid attempts. Retry in {lockout_secs}s."),
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = self
|
let _ = self
|
||||||
.send(
|
.send(&SendMessage::new(
|
||||||
"ℹ️ Telegram pairing is not active. Ask operator to update allowlist in config.toml.",
|
"ℹ️ Telegram pairing is not active. Ask operator to update allowlist in config.toml.",
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -456,23 +456,20 @@ Allowlist Telegram username (without '@') or numeric user ID.",
|
||||||
.unwrap_or_else(|| "YOUR_TELEGRAM_ID".to_string());
|
.unwrap_or_else(|| "YOUR_TELEGRAM_ID".to_string());
|
||||||
|
|
||||||
let _ = self
|
let _ = self
|
||||||
.send(
|
.send(&SendMessage::new(
|
||||||
&format!(
|
format!(
|
||||||
"🔐 This bot requires operator approval.\n\n\
|
"🔐 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."
|
||||||
Copy this command to operator terminal:\n\
|
|
||||||
`zeroclaw channel bind-telegram {suggested_identity}`\n\n\
|
|
||||||
After operator runs it, send your message again."
|
|
||||||
),
|
),
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if self.pairing_code_active() {
|
if self.pairing_code_active() {
|
||||||
let _ = self
|
let _ = self
|
||||||
.send(
|
.send(&SendMessage::new(
|
||||||
"ℹ️ If operator provides a one-time pairing code, you can also run `/bind <code>`.",
|
"ℹ️ If operator provides a one-time pairing code, you can also run `/bind <code>`.",
|
||||||
&chat_id,
|
&chat_id,
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1066,7 +1063,8 @@ impl Channel for TelegramChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(attachment) = parse_path_only_attachment(&message.content) {
|
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1369,7 +1367,7 @@ mod tests {
|
||||||
"username": "alice"
|
"username": "alice"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"id": -100200300
|
"id": -100_200_300
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct ChannelMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message to send through a channel
|
/// Message to send through a channel
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SendMessage {
|
pub struct SendMessage {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub recipient: 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
|
/// Core channel trait — implement for any messaging platform
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Channel: Send + Sync {
|
pub trait Channel: Send + Sync {
|
||||||
|
|
@ -152,7 +132,10 @@ mod tests {
|
||||||
assert!(channel.health_check().await);
|
assert!(channel.health_check().await);
|
||||||
assert!(channel.start_typing("bob").await.is_ok());
|
assert!(channel.start_typing("bob").await.is_ok());
|
||||||
assert!(channel.stop_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]
|
#[tokio::test]
|
||||||
|
|
|
||||||
|
|
@ -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::config::Config;
|
||||||
use crate::cron::{
|
use crate::cron::{
|
||||||
due_jobs, next_run_for_schedule, record_last_run, record_run, remove_job, reschedule_after_run,
|
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()
|
.as_ref()
|
||||||
.ok_or_else(|| anyhow::anyhow!("telegram channel not configured"))?;
|
.ok_or_else(|| anyhow::anyhow!("telegram channel not configured"))?;
|
||||||
let channel = TelegramChannel::new(tg.bot_token.clone(), tg.allowed_users.clone());
|
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" => {
|
"discord" => {
|
||||||
let dc = config
|
let dc = config
|
||||||
|
|
@ -247,7 +247,7 @@ async fn deliver_if_configured(config: &Config, job: &CronJob, output: &str) ->
|
||||||
dc.listen_to_bots,
|
dc.listen_to_bots,
|
||||||
dc.mention_only,
|
dc.mention_only,
|
||||||
);
|
);
|
||||||
channel.send(output, target).await?;
|
channel.send(&SendMessage::new(output, target)).await?;
|
||||||
}
|
}
|
||||||
"slack" => {
|
"slack" => {
|
||||||
let sl = config
|
let sl = config
|
||||||
|
|
@ -260,7 +260,7 @@ async fn deliver_if_configured(config: &Config, job: &CronJob, output: &str) ->
|
||||||
sl.channel_id.clone(),
|
sl.channel_id.clone(),
|
||||||
sl.allowed_users.clone(),
|
sl.allowed_users.clone(),
|
||||||
);
|
);
|
||||||
channel.send(output, target).await?;
|
channel.send(&SendMessage::new(output, target)).await?;
|
||||||
}
|
}
|
||||||
other => anyhow::bail!("unsupported delivery channel: {other}"),
|
other => anyhow::bail!("unsupported delivery channel: {other}"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -704,7 +704,10 @@ async fn handle_whatsapp_message(
|
||||||
{
|
{
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
// Send reply via WhatsApp
|
// 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}");
|
tracing::error!("Failed to send WhatsApp reply: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue