feat: 实现长消息分块发送功能,优化 WeChat 消息传递
This commit is contained in:
parent
fb90641774
commit
c3bfe32fa3
@ -5,7 +5,7 @@ use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use std::time::UNIX_EPOCH;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_util::FutureExt;
|
||||
@ -313,15 +313,37 @@ impl Channel for WechatChannel {
|
||||
let mut text_sent = false;
|
||||
|
||||
if !text.is_empty() {
|
||||
self.bot.send(&msg.chat_id, &text).await.map_err(|error| {
|
||||
ChannelError::SendError(format!("WeChat text send failed: {}", error))
|
||||
let chunks = split_text(&text, MAX_WECHAT_CHUNK_CHARS);
|
||||
if chunks.len() > 1 {
|
||||
tracing::info!(
|
||||
channel = %self.name,
|
||||
chat_id = %msg.chat_id,
|
||||
total_chars = text.len(),
|
||||
chunk_count = chunks.len(),
|
||||
"WeChat: splitting long message into chunks"
|
||||
);
|
||||
}
|
||||
for (i, chunk) in chunks.iter().enumerate() {
|
||||
if i > 0 {
|
||||
tokio::time::sleep(Duration::from_millis(CHUNK_SEND_INTERVAL_MS)).await;
|
||||
}
|
||||
self.bot.send(&msg.chat_id, chunk).await.map_err(|error| {
|
||||
ChannelError::SendError(format!(
|
||||
"WeChat text send failed (chunk {}/{}): {}",
|
||||
i + 1,
|
||||
chunks.len(),
|
||||
error
|
||||
))
|
||||
})?;
|
||||
tracing::info!(
|
||||
channel = %self.name,
|
||||
chat_id = %msg.chat_id,
|
||||
content_len = text.len(),
|
||||
chunk = i + 1,
|
||||
total_chunks = chunks.len(),
|
||||
content_len = chunk.len(),
|
||||
"WeChat text message sent"
|
||||
);
|
||||
}
|
||||
text_sent = true;
|
||||
}
|
||||
|
||||
@ -356,6 +378,51 @@ impl Channel for WechatChannel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Split text into chunks suitable for WeChat delivery.
|
||||
/// Prefers splitting at paragraph breaks, then newlines, then sentence boundaries.
|
||||
const MAX_WECHAT_CHUNK_CHARS: usize = 2000;
|
||||
const CHUNK_SEND_INTERVAL_MS: u64 = 500;
|
||||
|
||||
fn split_text(text: &str, limit: usize) -> Vec<String> {
|
||||
if text.len() <= limit {
|
||||
return vec![text.to_string()];
|
||||
}
|
||||
let mut chunks = Vec::new();
|
||||
let mut remaining = text;
|
||||
while !remaining.is_empty() {
|
||||
if remaining.len() <= limit {
|
||||
chunks.push(remaining.to_string());
|
||||
break;
|
||||
}
|
||||
let end = remaining.floor_char_boundary(limit);
|
||||
let window = &remaining[..end];
|
||||
let cut = window
|
||||
.rfind("\n\n")
|
||||
.filter(|&i| i > end * 3 / 10)
|
||||
.map(|i| i + 2)
|
||||
.or_else(|| {
|
||||
window
|
||||
.rfind('\n')
|
||||
.filter(|&i| i > end * 3 / 10)
|
||||
.map(|i| i + 1)
|
||||
})
|
||||
.or_else(|| {
|
||||
window
|
||||
.rfind('。')
|
||||
.filter(|&i| i > end * 3 / 10)
|
||||
.map(|i| i + 3)
|
||||
})
|
||||
.unwrap_or(end);
|
||||
chunks.push(remaining[..cut].to_string());
|
||||
remaining = &remaining[cut..];
|
||||
}
|
||||
if chunks.is_empty() {
|
||||
vec![String::new()]
|
||||
} else {
|
||||
chunks
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user