ferobot/client_sync.rs
1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3//
4// ferobot: async Telegram Bot API framework written in Rust
5// Repository: https://github.com/ankit-chaubey/ferobot
6//
7// Ferobot provides a fast and ergonomic framework for building Telegram bots
8// using the official Telegram Bot API.
9//
10// Author: Ankit Chaubey
11//
12// If you use or modify this code, keep this notice at the top of your file
13// and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
14
15//! Synchronous `ureq`-backed HTTP client (item 23).
16//!
17//! Enable with the `client-ureq` Cargo feature:
18//!
19//! ```toml
20//! ferobot = { version = "0.3", features = ["client-ureq"] }
21//! ```
22//!
23//! # Example
24//!
25//! ```rust,no_run
26//! use ferobot::client_sync::SyncBot;
27//!
28//! fn main() {
29//! let bot = SyncBot::new("YOUR_TOKEN");
30//! let me = bot.call_sync("getMe", serde_json::Value::Null).unwrap();
31//! println!("{me}");
32//! }
33//! ```
34
35use serde_json::Value;
36use ureq::Agent;
37
38use crate::BotError;
39
40/// A synchronous, blocking Telegram bot client backed by `ureq`.
41///
42/// This wraps the raw JSON API; all methods are blocking and return
43/// `Result<serde_json::Value, BotError>`. Use the async [`Bot`](crate::Bot)
44/// for production bots; `SyncBot` is best for scripts and CLI tools.
45#[derive(Debug, Clone)]
46pub struct SyncBot {
47 api_url: String,
48 agent: Agent,
49}
50
51impl SyncBot {
52 /// Create a `SyncBot` using the default Telegram API URL.
53 pub fn new(token: &str) -> Self {
54 Self::with_url(token, "https://api.telegram.org")
55 }
56
57 /// Create a `SyncBot` with a custom API URL (e.g. a local Bot API server).
58 pub fn with_url(token: &str, base_url: &str) -> Self {
59 let agent = ureq::AgentBuilder::new()
60 .timeout(std::time::Duration::from_secs(60))
61 .build();
62 Self {
63 api_url: format!("{}/bot{}", base_url.trim_end_matches('/'), token),
64 agent,
65 }
66 }
67
68 /// Call any Telegram Bot API method synchronously.
69 ///
70 /// - `method`: e.g. `"sendMessage"`.
71 /// - `params`: a JSON value; pass `Value::Null` for methods with no params.
72 pub fn call_sync(&self, method: &str, params: Value) -> Result<Value, BotError> {
73 let url = format!("{}/{}", self.api_url, method);
74
75 let response: Value = if params.is_null() {
76 self.agent
77 .post(&url)
78 .call()
79 .map_err(|e| BotError::Other(e.to_string()))?
80 .into_json()
81 .map_err(|e| BotError::Other(e.to_string()))?
82 } else {
83 self.agent
84 .post(&url)
85 .send_json(params)
86 .map_err(|e| BotError::Other(e.to_string()))?
87 .into_json()
88 .map_err(|e| BotError::Other(e.to_string()))?
89 };
90
91 if response["ok"].as_bool().unwrap_or(false) {
92 Ok(response["result"].clone())
93 } else {
94 let code = response["error_code"].as_i64().unwrap_or(0);
95 let description = response["description"]
96 .as_str()
97 .unwrap_or("unknown error")
98 .to_string();
99 let retry_after = response["parameters"]["retry_after"].as_i64();
100 let migrate_to_chat_id = response["parameters"]["migrate_to_chat_id"].as_i64();
101 Err(BotError::Api {
102 code,
103 description,
104 retry_after,
105 migrate_to_chat_id,
106 })
107 }
108 }
109}