DEV Community

Cover image for Use Telegram bot to receive 2FA verification code (OTP) with the help of Copilot
RustDesk
RustDesk

Posted on

Use Telegram bot to receive 2FA verification code (OTP) with the help of Copilot

One week ago, I saw a post on RustDesk subreddit, https://www.reddit.com/r/rustdesk/comments/1dk4mzp/how_can_i_be_notified_track_when_someone_connects/. It gave me hint to send the notification to Telegram bot, because I had been using it to receive news and signals while I traded FX before. This also inspired to me to receive OTP through Telegram bot.

Talk is cheap, show me the code.

So I started to ask Copilot.

  • "Please write a function to send OTP to telegram bot"
async fn send_otp_to_telegram(bot_token: &str, chat_id: &str, otp: &str) -> Result<()> {
    let client = Client::new();
    let url = format!("https://api.telegram.org/bot{}/sendMessage", bot_token);
    let payload = json!({
        "chat_id": chat_id,
        "text": otp
    });

    let response = client.post(&url)
        .json(&payload)
        .send()
        .await?;

    if response.status().is_success() {
        Ok(())
    } else {
        Err(anyhow!("Failed to send OTP to Telegram: {}", response.status()))
    }
}
Enter fullscreen mode Exit fullscreen mode
  • "Please write a function to send OTP to telegram bot, use reqwest"
use reqwest::Client;
use serde_json::json;
use std::error::Error;

async fn send_otp_to_telegram(otp: &str, bot_token: &str, chat_id: &str) -> Result<(), Box<dyn Error>> {
    let client = Client::new();
    let telegram_api_url = format!("https://api.telegram.org/bot{}/sendMessage", bot_token);
    let message = format!("Your OTP is: {}", otp);

    let response = client.post(&telegram_api_url)
        .json(&json!({
            "chat_id": chat_id,
            "text": message,
        }))
        .send()
        .await?;

    if response.status().is_success() {
        Ok(())
    } else {
        Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "Failed to send OTP")))
    }
}
Enter fullscreen mode Exit fullscreen mode
  • "How can I get telegram bot chat id with reqwest?"
async fn fetch_telegram_chat_id(bot_token: &str) -> Result<Vec<i64>, Box<dyn Error>> {
    let client = Client::new();
    let url = format!("https://api.telegram.org/bot{}/getUpdates", bot_token);

    let res = client.get(&url).send().await?;
    let body = res.text().await?;
    let json: Value = serde_json::from_str(&body)?;

    let mut chat_ids = Vec::new();
    if let Some(updates) = json["result"].as_array() {
        for update in updates {
            if let Some(chat_id) = update["message"]["chat"]["id"].as_i64() {
                chat_ids.push(chat_id);
            }
        }
    }

    Ok(chat_ids)
}
Enter fullscreen mode Exit fullscreen mode

After some polish, I finished my job,

// https://gist.github.com/dideler/85de4d64f66c1966788c1b2304b9caf1
pub async fn send_2fa_code_to_telegram(text: &str, bot: TelegramBot) -> ResultType<()> {
    let url = format!("https://api.telegram.org/bot{}/sendMessage", bot.token_str);
    let params = serde_json::json!({"chat_id": bot.chat_id, "text": text});
    crate::post_request(url, params.to_string(), "").await?;
    Ok(())
}

pub fn get_chatid_telegram(bot_token: &str) -> ResultType<Option<String>> {
    let url = format!("https://api.telegram.org/bot{}/getUpdates", bot_token);
    // because caller is in tokio runtime, so we must call post_request_sync in new thread.
    let handle = std::thread::spawn(move || {
        crate::post_request_sync(url, "".to_owned(), "")
    });
    let resp = handle.join().map_err(|_| anyhow!("Thread panicked"))??;
    let value = serde_json::from_str::<serde_json::Value>(&resp).map_err(|e| anyhow!(e))?;

    // Check for an error_code in the response
    if let Some(error_code) = value.get("error_code").and_then(|code| code.as_i64()) {
        // If there's an error_code, try to use the description for the error message
        let description = value["description"]
            .as_str()
            .unwrap_or("Unknown error occurred");
        return Err(anyhow!(
            "Telegram API error: {} (error_code: {})",
            description,
            error_code
        ));
    }

    let chat_id = &value["result"][0]["message"]["chat"]["id"];
    let chat_id = if let Some(id) = chat_id.as_i64() {
        Some(id.to_string())
    } else if let Some(id) = chat_id.as_str() {
        Some(id.to_owned())
    } else {
        None
    };

    if let Some(chat_id) = chat_id.as_ref() {
        let bot = TelegramBot {
            token_str: bot_token.to_owned(),
            chat_id: chat_id.to_owned(),
            ..Default::default()
        };
        bot.save()?;
    }

    Ok(chat_id)
}
Enter fullscreen mode Exit fullscreen mode
void changeBot({Function()? callback}) async {
  if (bind.mainHasValidBotSync()) {
    await bind.mainSetOption(key: "bot", value: "");
    callback?.call();
    return;
  }
  String errorText = '';
  bool loading = false;
  final controller = TextEditingController();
  gFFI.dialogManager.show((setState, close, context) {
    onVerify() async {
      final token = controller.text.trim();
      if (token == "") return;
      loading = true;
      errorText = '';
      setState(() {});
      final error = await bind.mainVerifyBot(token: token);
      if (error == "") {
        callback?.call();
        close();
      } else {
        errorText = translate(error);
        loading = false;
        setState(() {});
      }
    }

    final codeField = TextField(
      autofocus: true,
      controller: controller,
      decoration: InputDecoration(
        hintText: translate('Token'), 
      ),
    );

    return CustomAlertDialog(
      title: Text(translate("Telegram bot")),
      content: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SelectableText(translate("enable-bot-desc"),
                  style: TextStyle(fontSize: 12))
              .marginOnly(bottom: 12),
          Row(children: [Expanded(child: codeField)]),
          if (errorText != '')
            Text(errorText, style: TextStyle(color: Colors.red))
                .marginOnly(top: 12),
        ],
      ),
      actions: [
        dialogButton("Cancel", onPressed: close, isOutline: true),
        loading
            ? CircularProgressIndicator()
            : dialogButton("OK", onPressed: onVerify),
      ],
      onCancel: close,
    );
  });
}
Enter fullscreen mode Exit fullscreen mode

The UI to set bot token and also get chat id implicitly

Thanks to Copilot.

Top comments (0)