use async_trait::async_trait;
use robit_agent::error::Result;
#[derive(Debug, Clone)]
pub struct PlatformCaps {
pub supports_edit: bool,
pub returns_msg_id: bool,
pub supports_markdown: bool,
pub markdown_features: MarkdownFeatures,
pub max_message_length: usize,
pub supports_images: bool,
pub supports_files: bool,
pub max_upload_size: u64,
}
#[derive(Debug, Clone, Default)]
pub struct MarkdownFeatures {
pub headings: bool,
pub bold: bool,
pub italic: bool,
pub code_blocks: bool,
pub inline_code: bool,
pub links: bool,
pub unordered_lists: bool,
pub ordered_lists: bool,
pub blockquotes: bool,
pub tables: bool,
pub task_lists: bool,
pub images: bool,
pub strikethrough: bool,
}
impl MarkdownFeatures {
pub fn qq() -> Self {
Self {
headings: false, bold: false, italic: false, code_blocks: true, inline_code: false, links: false, unordered_lists: false,
ordered_lists: false,
blockquotes: false,
tables: false,
task_lists: false,
images: false,
strikethrough: false,
}
}
pub fn feishu() -> Self {
Self {
headings: true,
bold: true,
italic: true,
code_blocks: true,
inline_code: true,
links: true,
unordered_lists: true,
ordered_lists: true,
blockquotes: true,
tables: true,
task_lists: true,
images: true,
strikethrough: true,
}
}
}
impl PlatformCaps {
pub fn qq() -> Self {
Self {
supports_edit: true,
returns_msg_id: true,
supports_markdown: true, markdown_features: MarkdownFeatures::qq(),
max_message_length: 2000,
supports_images: true,
supports_files: true,
max_upload_size: 20 * 1024 * 1024, }
}
pub fn feishu() -> Self {
Self {
supports_edit: true,
returns_msg_id: true,
supports_markdown: true,
markdown_features: MarkdownFeatures::feishu(),
max_message_length: 30000,
supports_images: true,
supports_files: true,
max_upload_size: 50 * 1024 * 1024, }
}
}
#[derive(Debug, Clone)]
pub struct SendResult {
pub msg_id: String,
}
#[derive(Debug, Clone)]
pub struct UploadResult {
pub file_id: String,
pub url: String,
}
#[derive(Debug, Clone)]
pub struct MediaAttachment {
pub content_type: String,
pub url: String,
pub filename: Option<String>,
pub size: Option<u64>,
pub width: Option<u32>,
pub height: Option<u32>,
}
impl MediaAttachment {
pub fn is_image(&self) -> bool {
self.content_type.starts_with("image/")
}
pub fn describe(&self) -> String {
let kind = if self.is_image() { "图片" } else { "文件" };
let name = self
.filename
.as_deref()
.unwrap_or_else(|| if self.is_image() { "image" } else { "file" });
let size_str = self
.size
.map(|s| format!(" ({:.1}KB)", s as f64 / 1024.0))
.unwrap_or_default();
format!("[用户发送了{}: {}{}]", kind, name, size_str)
}
}
impl From<MediaAttachment> for robit_agent::event::MediaAttachment {
fn from(att: MediaAttachment) -> Self {
Self {
content_type: att.content_type,
url: att.url,
filename: att.filename,
size: att.size,
width: att.width,
height: att.height,
}
}
}
#[derive(Debug, Clone)]
pub struct SenderInfo {
pub user_id: String,
pub chat_id: String,
pub chat_type: ChatType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChatType {
Private,
Group,
}
#[derive(Debug, Clone)]
pub struct ChatMessage {
pub text: String,
pub sender: SenderInfo,
pub attachments: Vec<MediaAttachment>,
}
#[derive(Debug)]
pub enum PlatformEvent {
Message(ChatMessage),
Disconnected,
Other(serde_json::Value),
}
#[async_trait]
pub trait PlatformAdapter: Send + Sync + 'static {
fn capabilities() -> PlatformCaps;
async fn send_message(&self, chat_id: &str, text: &str) -> Result<SendResult>;
async fn edit_message(&self, chat_id: &str, _msg_id: &str, text: &str) -> Result<()> {
let _ = self.send_message(chat_id, text).await;
Ok(())
}
async fn upload_file(
&self,
_chat_id: &str,
_file_path: &str,
_media_type: &str,
) -> Result<UploadResult> {
Err(robit_agent::error::AgentError::InternalError(
"File upload not supported on this platform".into(),
))
}
async fn send_media_message(
&self,
chat_id: &str,
file_url: &str,
file_name: &str,
media_type: &str,
) -> Result<SendResult> {
let _ = file_url;
let label = if media_type == "image" { "📷" } else { "📎" };
let text = format!("{} {}", label, file_name);
self.send_message(chat_id, &text).await
}
async fn recv_event(&self) -> Result<PlatformEvent>;
}