telegram_bot_oxidebot 0.1.4

Telegram Bot for oxidebot framework
Documentation
use anyhow::{Context as _, Result};
use oxidebot::source::{
    group::{Group, GroupProfile},
    user::{User, UserProfile},
};

use crate::telegram::{Chat, User as TelegramUser};

pub fn telegram_user_name(user: &TelegramUser) -> String {
    user.username.clone().unwrap_or_else(|| {
        let name = format!(
            "{} {}",
            user.first_name,
            user.last_name.as_deref().unwrap_or_default()
        );
        name.trim().to_owned()
    })
}

pub fn parse_user(user: TelegramUser) -> User {
    User {
        id: user.id.to_string(),
        profile: Some(UserProfile {
            nickname: Some(telegram_user_name(&user)),
            ..Default::default()
        }),
        group_info: None,
    }
}

pub fn parse_chat_sender(chat: Chat) -> User {
    let nickname = chat.title.clone().or(chat.username.clone()).or_else(|| {
        let name = format!(
            "{} {}",
            chat.first_name.as_deref().unwrap_or_default(),
            chat.last_name.as_deref().unwrap_or_default()
        );
        (!name.trim().is_empty()).then(|| name.trim().to_owned())
    });
    User {
        id: chat.id.to_string(),
        profile: Some(UserProfile {
            nickname,
            ..Default::default()
        }),
        group_info: None,
    }
}

pub fn parse_group(group: Chat) -> Group {
    Group {
        id: group.id.to_string(),
        profile: Some(GroupProfile {
            name: group.title.or(group.username),
            avatar: None,
            member_count: None,
        }),
    }
}

pub fn message_id(chat_id: i64, message_id: i64) -> String {
    format!("{chat_id}_{message_id}")
}

pub fn split_id(id: impl AsRef<str>) -> Result<(String, i64)> {
    let id = id.as_ref();
    let (chat_id, message_id) = id
        .split_once('_')
        .ok_or_else(|| anyhow::anyhow!("invalid Telegram message id {id:?}"))?;
    anyhow::ensure!(!chat_id.is_empty(), "Telegram chat id is empty");
    anyhow::ensure!(
        !message_id.contains('_'),
        "invalid Telegram message id {id:?}"
    );
    let message_id = message_id
        .parse::<i64>()
        .with_context(|| format!("invalid Telegram message number in {id:?}"))?;
    Ok((chat_id.to_owned(), message_id))
}

pub fn join_request_id(chat_id: i64, user_id: i64) -> String {
    format!("{chat_id}:{user_id}")
}

pub fn split_join_request_id(id: &str) -> Result<(i64, i64)> {
    let (chat_id, user_id) = id
        .split_once(':')
        .ok_or_else(|| anyhow::anyhow!("invalid Telegram join-request id {id:?}"))?;
    Ok((
        chat_id
            .parse()
            .with_context(|| format!("invalid chat id in join-request id {id:?}"))?,
        user_id
            .parse()
            .with_context(|| format!("invalid user id in join-request id {id:?}"))?,
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn message_id_round_trip_supports_negative_chat_ids() {
        assert_eq!(
            split_id(message_id(-1_001_234, 56)).unwrap(),
            ("-1001234".to_owned(), 56)
        );
    }

    #[test]
    fn message_id_rejects_trailing_components() {
        assert!(split_id("-100_12_extra").is_err());
    }

    #[test]
    fn join_request_id_round_trip() {
        let id = join_request_id(-100, 200);
        assert_eq!(split_join_request_id(&id).unwrap(), (-100, 200));
    }
}