layer_client/
typing_guard.rs1use crate::{Client, InvocationError, PeerRef};
24use layer_tl_types as tl;
25use std::sync::Arc;
26use std::time::Duration;
27use tokio::sync::Notify;
28use tokio::task::JoinHandle;
29
30pub struct TypingGuard {
37 stop: Arc<Notify>,
38 task: Option<JoinHandle<()>>,
39}
40
41impl TypingGuard {
42 pub async fn start(
44 client: &Client,
45 peer: impl Into<PeerRef>,
46 action: tl::enums::SendMessageAction,
47 ) -> Result<Self, InvocationError> {
48 let peer = peer.into().resolve(client).await?;
49 Self::start_ex(client, peer, action, None, Duration::from_secs(4)).await
50 }
51
52 pub async fn start_ex(
62 client: &Client,
63 peer: tl::enums::Peer,
64 action: tl::enums::SendMessageAction,
65 topic_id: Option<i32>,
66 repeat_delay: Duration,
67 ) -> Result<Self, InvocationError> {
68 client
70 .send_chat_action_ex(peer.clone(), action.clone(), topic_id)
71 .await?;
72
73 let stop = Arc::new(Notify::new());
74 let stop2 = stop.clone();
75 let client = client.clone();
76
77 let task = tokio::spawn(async move {
78 loop {
79 tokio::select! {
80 _ = tokio::time::sleep(repeat_delay) => {
81 if let Err(e) = client.send_chat_action_ex(peer.clone(), action.clone(), topic_id).await {
82 tracing::warn!("[typing_guard] Failed to refresh typing action: {e}");
83 break;
84 }
85 }
86 _ = stop2.notified() => break,
87 }
88 }
89 let cancel = tl::enums::SendMessageAction::SendMessageCancelAction;
91 let _ = client
92 .send_chat_action_ex(peer.clone(), cancel, topic_id)
93 .await;
94 });
95
96 Ok(Self {
97 stop,
98 task: Some(task),
99 })
100 }
101
102 pub fn cancel(&mut self) {
104 self.stop.notify_one();
105 }
106}
107
108impl Drop for TypingGuard {
109 fn drop(&mut self) {
110 self.stop.notify_one();
111 if let Some(t) = self.task.take() {
112 t.abort();
113 }
114 }
115}
116
117impl Client {
120 pub async fn typing(&self, peer: impl Into<PeerRef>) -> Result<TypingGuard, InvocationError> {
124 TypingGuard::start(
125 self,
126 peer,
127 tl::enums::SendMessageAction::SendMessageTypingAction,
128 )
129 .await
130 }
131
132 pub async fn typing_in_topic(
136 &self,
137 peer: impl Into<PeerRef>,
138 topic_id: i32,
139 ) -> Result<TypingGuard, InvocationError> {
140 let peer = peer.into().resolve(self).await?;
141 TypingGuard::start_ex(
142 self,
143 peer,
144 tl::enums::SendMessageAction::SendMessageTypingAction,
145 Some(topic_id),
146 std::time::Duration::from_secs(4),
147 )
148 .await
149 }
150
151 pub async fn uploading_document(
153 &self,
154 peer: impl Into<PeerRef>,
155 ) -> Result<TypingGuard, InvocationError> {
156 TypingGuard::start(
157 self,
158 peer,
159 tl::enums::SendMessageAction::SendMessageUploadDocumentAction(
160 tl::types::SendMessageUploadDocumentAction { progress: 0 },
161 ),
162 )
163 .await
164 }
165
166 pub async fn recording_video(
168 &self,
169 peer: impl Into<PeerRef>,
170 ) -> Result<TypingGuard, InvocationError> {
171 TypingGuard::start(
172 self,
173 peer,
174 tl::enums::SendMessageAction::SendMessageRecordVideoAction,
175 )
176 .await
177 }
178
179 pub(crate) async fn send_chat_action_ex(
181 &self,
182 peer: tl::enums::Peer,
183 action: tl::enums::SendMessageAction,
184 topic_id: Option<i32>,
185 ) -> Result<(), InvocationError> {
186 let input_peer = self.inner.peer_cache.read().await.peer_to_input(&peer);
187 let req = tl::functions::messages::SetTyping {
188 peer: input_peer,
189 top_msg_id: topic_id,
190 action,
191 };
192 self.rpc_write(&req).await
193 }
194}