1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
//
// ferogram: async Telegram MTProto client in Rust
// https://github.com/ankit-chaubey/ferogram
//
// Licensed under either the MIT License or the Apache License 2.0.
// See the LICENSE-MIT or LICENSE-APACHE file in this repository:
// https://github.com/ankit-chaubey/ferogram
//
// Feel free to use, modify, and share this code.
// Please keep this notice when redistributing.
//! [`Client::quick_connect`] - connect and authenticate in one call.
//!
//! For advanced options (proxy, PFS, custom transport, catch-up, etc.)
//! use [`Client::builder()`] directly.
use std::io::{self, Write};
use crate::{Client, InvocationError, ShutdownToken, SignInError, builder::BuilderError};
impl Client {
/// Connect and authenticate in a single call.
///
/// Prompts interactively (stdin) for a phone number or bot token, then
/// drives the full auth flow - login code and 2FA password if required.
/// If the session is already authorized the prompt is skipped entirely.
///
/// For advanced options (proxy, custom transport, PFS, catch-up, etc.)
/// use [`Client::builder()`] instead.
///
/// # Example
///
/// ```rust,no_run
/// use ferogram::Client;
///
/// const API_ID: i32 = 0;
/// const API_HASH: &str = "";
///
/// # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let (client, _) = Client::quick_connect("my.session", API_ID, API_HASH).await?;
/// # Ok(()) }
/// ```
pub async fn quick_connect(
session: impl AsRef<std::path::Path>,
api_id: i32,
api_hash: &str,
) -> Result<(Client, ShutdownToken), QuickConnectError> {
let (client, shutdown) = Client::builder()
.session(session)
.api_id(api_id)
.api_hash(api_hash)
.connect()
.await?;
if client
.is_authorized()
.await
.map_err(QuickConnectError::Auth)?
{
return Ok((client, shutdown));
}
let credential = prompt("Enter phone number or bot token: ")?;
if is_bot_token(&credential) {
client
.bot_sign_in(&credential)
.await
.map_err(QuickConnectError::Auth)?;
} else {
sign_in_user(&client, &credential).await?;
}
client
.save_session()
.await
.map_err(QuickConnectError::Auth)?;
Ok((client, shutdown))
}
}
async fn sign_in_user(client: &Client, phone: &str) -> Result<(), QuickConnectError> {
let token = client
.request_login_code(phone)
.await
.map_err(QuickConnectError::Auth)?;
let code = prompt("Enter the login code: ")?;
match client.sign_in(&token, &code).await {
Ok(_) => {}
Err(SignInError::PasswordRequired(pw_token)) => {
let password = prompt("Enter your 2FA password: ")?;
client
.check_password(*pw_token, password.as_bytes())
.await
.map_err(QuickConnectError::Auth)?;
}
Err(SignInError::InvalidCode) => return Err(QuickConnectError::InvalidCode),
Err(SignInError::SignUpRequired) => return Err(QuickConnectError::SignUpRequired),
Err(SignInError::Other(e)) => return Err(QuickConnectError::Auth(e)),
}
Ok(())
}
// Bot tokens are always `<digits>:<alphanumeric>`, e.g. `123456789:AABBcc...`
fn is_bot_token(s: &str) -> bool {
match s.split_once(':') {
Some((id, _)) => !id.is_empty() && id.chars().all(|c| c.is_ascii_digit()),
None => false,
}
}
fn prompt(msg: &str) -> Result<String, QuickConnectError> {
print!("{msg}");
io::stdout().flush().map_err(QuickConnectError::Io)?;
let mut buf = String::new();
io::stdin()
.read_line(&mut buf)
.map_err(QuickConnectError::Io)?;
Ok(buf.trim().to_string())
}
/// Errors returned by [`Client::quick_connect`].
#[derive(Debug)]
pub enum QuickConnectError {
/// [`ClientBuilder::connect`] failed (missing api_id/hash or network error).
Builder(BuilderError),
/// An MTProto RPC call during authentication failed.
Auth(InvocationError),
/// The login code entered was incorrect.
InvalidCode,
/// Phone number is not registered on Telegram.
SignUpRequired,
/// Failed to read from stdin while prompting.
Io(std::io::Error),
}
impl From<BuilderError> for QuickConnectError {
fn from(e: BuilderError) -> Self {
Self::Builder(e)
}
}
impl std::fmt::Display for QuickConnectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Builder(e) => write!(f, "quick_connect: {e}"),
Self::Auth(e) => write!(f, "quick_connect: auth error: {e}"),
Self::InvalidCode => f.write_str("quick_connect: invalid login code"),
Self::SignUpRequired => f.write_str("quick_connect: phone not registered"),
Self::Io(e) => write!(f, "quick_connect: stdin error: {e}"),
}
}
}
impl std::error::Error for QuickConnectError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Builder(e) => Some(e),
Self::Auth(e) => Some(e),
Self::Io(e) => Some(e),
_ => None,
}
}
}