1use std::any::Any;
2
3use botkit_core::action::ChatActionSender;
4use botkit_core::{ContextData, OptionValue};
5
6use crate::action::TelegramActionSender;
7use crate::client::TelegramClient;
8use crate::types::{EntityType, Update, UpdateKind};
9
10pub struct TelegramContextData {
12 pub update: Update,
13 client: TelegramClient,
15 channel_id: String,
17 chat_id: i64,
18 user_id: String,
19 user_name: String,
20 command_name: Option<String>,
21 command_args: Option<String>,
22 button_id: Option<String>,
23 message_content: Option<String>,
24}
25
26impl TelegramContextData {
27 pub fn new(update: Update, client: TelegramClient) -> Self {
28 let (chat_id, user_id, user_name, message_content) = match &update.kind {
29 UpdateKind::Message(m) | UpdateKind::EditedMessage(m) => {
30 let user = m.from.as_ref();
31 (
32 m.chat.id,
33 user.map(|u| u.id.to_string()).unwrap_or_default(),
34 user.map(|u| u.first_name.clone()).unwrap_or_default(),
35 m.text.clone(),
36 )
37 }
38 UpdateKind::CallbackQuery(cq) => {
39 let chat_id = cq.message.as_ref().map(|m| m.chat.id).unwrap_or(0);
40 (
41 chat_id,
42 cq.from.id.to_string(),
43 cq.from.first_name.clone(),
44 cq.message.as_ref().and_then(|m| m.text.clone()),
45 )
46 }
47 _ => (0, String::new(), String::new(), None),
48 };
49
50 let (command_name, command_args) = Self::extract_command(&update);
51
52 let button_id = match &update.kind {
53 UpdateKind::CallbackQuery(cq) => cq.data.clone(),
54 _ => None,
55 };
56
57 Self {
58 update,
59 client,
60 channel_id: chat_id.to_string(),
61 chat_id,
62 user_id,
63 user_name,
64 command_name,
65 command_args,
66 button_id,
67 message_content,
68 }
69 }
70
71 pub fn client(&self) -> &TelegramClient {
73 &self.client
74 }
75
76 pub fn chat_id(&self) -> i64 {
78 self.chat_id
79 }
80
81 fn extract_command(update: &Update) -> (Option<String>, Option<String>) {
82 let message = match &update.kind {
83 UpdateKind::Message(m) => m,
84 _ => return (None, None),
85 };
86
87 let text = match &message.text {
88 Some(t) => t,
89 None => return (None, None),
90 };
91
92 let entities = match &message.entities {
93 Some(e) => e,
94 None => return (None, None),
95 };
96
97 let cmd_entity = entities
99 .iter()
100 .find(|e| matches!(e.entity_type, EntityType::BotCommand) && e.offset == 0);
101
102 let cmd_entity = match cmd_entity {
103 Some(e) => e,
104 None => return (None, None),
105 };
106
107 let cmd_text = &text[..cmd_entity.length as usize];
108 let name = cmd_text
110 .trim_start_matches('/')
111 .split('@')
112 .next()
113 .unwrap_or("")
114 .to_string();
115
116 let args = text[cmd_entity.length as usize..].trim().to_string();
117 let args = if args.is_empty() { None } else { Some(args) };
118
119 (Some(name), args)
120 }
121}
122
123impl ContextData for TelegramContextData {
124 fn channel_id(&self) -> &str {
125 &self.channel_id
126 }
127
128 fn user_id(&self) -> &str {
129 &self.user_id
130 }
131
132 fn user_name(&self) -> &str {
133 &self.user_name
134 }
135
136 fn command_name(&self) -> Option<&str> {
137 self.command_name.as_deref()
138 }
139
140 fn command_args(&self) -> Option<&str> {
141 self.command_args.as_deref()
142 }
143
144 fn option(&self, _name: &str) -> Option<OptionValue> {
145 None
147 }
148
149 fn button_id(&self) -> Option<&str> {
150 self.button_id.as_deref()
151 }
152
153 fn message_content(&self) -> Option<&str> {
154 self.message_content.as_deref()
155 }
156
157 fn as_any(&self) -> &dyn Any {
158 self
159 }
160
161 fn action_sender(&self) -> Option<Box<dyn ChatActionSender>> {
162 if self.chat_id == 0 {
163 return None;
164 }
165 Some(Box::new(TelegramActionSender::new(
166 self.client.clone(),
167 self.chat_id,
168 )))
169 }
170}