1use async_trait::async_trait;
10use robit_agent::error::Result;
11
12#[derive(Debug, Clone)]
14pub struct PlatformCaps {
15 pub supports_edit: bool,
16 pub returns_msg_id: bool,
17 pub supports_markdown: bool,
18 pub markdown_features: MarkdownFeatures,
19 pub max_message_length: usize,
20 pub supports_images: bool,
22 pub supports_files: bool,
24 pub max_upload_size: u64,
26}
27
28#[derive(Debug, Clone, Default)]
30pub struct MarkdownFeatures {
31 pub headings: bool,
32 pub bold: bool,
33 pub italic: bool,
34 pub code_blocks: bool,
35 pub inline_code: bool,
36 pub links: bool,
37 pub unordered_lists: bool,
38 pub ordered_lists: bool,
39 pub blockquotes: bool,
40 pub tables: bool,
41 pub task_lists: bool,
42 pub images: bool,
43 pub strikethrough: bool,
44}
45
46impl MarkdownFeatures {
47 pub fn qq() -> Self {
50 Self {
51 headings: false, bold: false, italic: false, code_blocks: true, inline_code: false, links: false, unordered_lists: false,
58 ordered_lists: false,
59 blockquotes: false,
60 tables: false,
61 task_lists: false,
62 images: false,
63 strikethrough: false,
64 }
65 }
66
67 pub fn feishu() -> Self {
69 Self {
70 headings: true,
71 bold: true,
72 italic: true,
73 code_blocks: true,
74 inline_code: true,
75 links: true,
76 unordered_lists: true,
77 ordered_lists: true,
78 blockquotes: true,
79 tables: true,
80 task_lists: true,
81 images: true,
82 strikethrough: true,
83 }
84 }
85}
86
87impl PlatformCaps {
88 pub fn qq() -> Self {
92 Self {
93 supports_edit: true,
94 returns_msg_id: true,
95 supports_markdown: true, markdown_features: MarkdownFeatures::qq(),
97 max_message_length: 2000,
98 supports_images: true,
99 supports_files: true,
100 max_upload_size: 20 * 1024 * 1024, }
102 }
103
104 pub fn feishu() -> Self {
106 Self {
107 supports_edit: true,
108 returns_msg_id: true,
109 supports_markdown: true,
110 markdown_features: MarkdownFeatures::feishu(),
111 max_message_length: 30000,
112 supports_images: true,
113 supports_files: true,
114 max_upload_size: 50 * 1024 * 1024, }
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct SendResult {
122 pub msg_id: String,
123}
124
125#[derive(Debug, Clone)]
127pub struct UploadResult {
128 pub file_id: String,
130 pub url: String,
132}
133
134#[derive(Debug, Clone)]
136pub struct MediaAttachment {
137 pub content_type: String,
139 pub url: String,
141 pub filename: Option<String>,
143 pub size: Option<u64>,
145 pub width: Option<u32>,
147 pub height: Option<u32>,
149}
150
151impl MediaAttachment {
152 pub fn is_image(&self) -> bool {
154 self.content_type.starts_with("image/")
155 }
156
157 pub fn describe(&self) -> String {
159 let kind = if self.is_image() { "图片" } else { "文件" };
160 let name = self
161 .filename
162 .as_deref()
163 .unwrap_or_else(|| if self.is_image() { "image" } else { "file" });
164 let size_str = self
165 .size
166 .map(|s| format!(" ({:.1}KB)", s as f64 / 1024.0))
167 .unwrap_or_default();
168 format!("[用户发送了{}: {}{}]", kind, name, size_str)
169 }
170}
171
172impl From<MediaAttachment> for robit_agent::event::MediaAttachment {
174 fn from(att: MediaAttachment) -> Self {
175 Self {
176 content_type: att.content_type,
177 url: att.url,
178 filename: att.filename,
179 size: att.size,
180 width: att.width,
181 height: att.height,
182 }
183 }
184}
185
186#[derive(Debug, Clone)]
188pub struct SenderInfo {
189 pub user_id: String,
190 pub chat_id: String,
191 pub chat_type: ChatType,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum ChatType {
196 Private,
197 Group,
198}
199
200#[derive(Debug, Clone)]
202pub struct ChatMessage {
203 pub text: String,
204 pub sender: SenderInfo,
205 pub attachments: Vec<MediaAttachment>,
207}
208
209#[derive(Debug)]
211pub enum PlatformEvent {
212 Message(ChatMessage),
213 Disconnected,
214 Other(serde_json::Value),
215}
216
217#[async_trait]
225pub trait PlatformAdapter: Send + Sync + 'static {
226 fn capabilities() -> PlatformCaps;
229
230 async fn send_message(&self, chat_id: &str, text: &str) -> Result<SendResult>;
232
233 async fn edit_message(&self, chat_id: &str, _msg_id: &str, text: &str) -> Result<()> {
236 let _ = self.send_message(chat_id, text).await;
237 Ok(())
238 }
239
240 async fn upload_file(
249 &self,
250 _chat_id: &str,
251 _file_path: &str,
252 _media_type: &str,
253 ) -> Result<UploadResult> {
254 Err(robit_agent::error::AgentError::InternalError(
255 "File upload not supported on this platform".into(),
256 ))
257 }
258
259 async fn send_media_message(
262 &self,
263 chat_id: &str,
264 file_url: &str,
265 file_name: &str,
266 media_type: &str,
267 ) -> Result<SendResult> {
268 let _ = file_url;
269 let label = if media_type == "image" { "📷" } else { "📎" };
270 let text = format!("{} {}", label, file_name);
271 self.send_message(chat_id, &text).await
272 }
273
274 async fn recv_event(&self) -> Result<PlatformEvent>;
276}