use crate::PeerCache;
use ferogram_tl_types as tl;
const ZERO_CHANNEL_ID: i64 = -1_000_000_000_000;
#[derive(Clone, Debug)]
pub enum PeerRef {
Username(String),
Id(i64),
Peer(tl::enums::Peer),
Input(tl::enums::InputPeer),
InviteHash(String),
Phone(String),
}
impl PeerRef {
pub fn parse_invite_hash(link: &str) -> Option<&str> {
if let Some(rest) = link.strip_prefix("tg://join?invite=") {
let hash = rest.split('&').next().unwrap_or(rest);
if !hash.is_empty() {
return Some(hash);
}
}
if let Some(pos) = link.find("/+") {
let hash = &link[pos + 2..];
if !hash.is_empty() {
return Some(hash.split('?').next().unwrap_or(hash));
}
}
if let Some(pos) = link.find("/joinchat/") {
let hash = &link[pos + 10..];
if !hash.is_empty() {
return Some(hash.split('?').next().unwrap_or(hash));
}
}
None
}
pub async fn resolve(
self,
client: &crate::Client,
) -> Result<tl::enums::Peer, crate::InvocationError> {
match self {
PeerRef::Peer(p) => Ok(p),
PeerRef::Id(id) => resolve_id(id, client).await,
PeerRef::Input(ip) => {
{
let mut cache: tokio::sync::RwLockWriteGuard<'_, PeerCache> =
client.inner.peer_cache.write().await;
cache.cache_input_peer(&ip);
}
input_peer_to_peer(ip)
}
PeerRef::Username(s) => {
let s = s.trim().trim_start_matches('@').to_owned();
if s == "me" || s == "self" {
return Ok(tl::enums::Peer::User(tl::types::PeerUser { user_id: 0 }));
}
if let Ok(id) = s.parse::<i64>() {
return resolve_id(id, client).await;
}
{
let cache: tokio::sync::RwLockReadGuard<'_, PeerCache> =
client.inner.peer_cache.read().await;
if let Some(&(id, ref ty)) = cache.username_to_peer.get(&s.to_lowercase()) {
let peer = match ty {
crate::PeerType::User => {
tl::enums::Peer::User(tl::types::PeerUser { user_id: id })
}
crate::PeerType::Channel => {
tl::enums::Peer::Channel(tl::types::PeerChannel { channel_id: id })
}
crate::PeerType::Chat => {
tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: id })
}
};
if cache.peer_to_input(&peer).is_ok() {
return Ok(peer);
}
}
}
client.resolve_username_rpc(&s).await
}
PeerRef::Phone(phone) => {
{
let cache: tokio::sync::RwLockReadGuard<'_, PeerCache> =
client.inner.peer_cache.read().await;
if let Some(&uid) = cache.phone_to_user.get(&phone)
&& cache.user_input_peer(uid).is_ok()
{
return Ok(tl::enums::Peer::User(tl::types::PeerUser { user_id: uid }));
}
}
client.resolve_phone_rpc(&phone).await
}
PeerRef::InviteHash(hash) => client.resolve_invite_hash_rpc(&hash).await,
}
}
}
async fn resolve_id(
id: i64,
client: &crate::Client,
) -> Result<tl::enums::Peer, crate::InvocationError> {
let decoded = decode_bot_api_id(id);
if matches!(decoded, tl::enums::Peer::Chat(_)) {
return Ok(decoded);
}
{
let cache: tokio::sync::RwLockReadGuard<'_, PeerCache> =
client.inner.peer_cache.read().await;
if cache.peer_to_input(&decoded).is_ok() {
return Ok(decoded);
}
}
client.fetch_by_id_rpc(decoded).await
}
fn decode_bot_api_id(id: i64) -> tl::enums::Peer {
if id > 0 {
tl::enums::Peer::User(tl::types::PeerUser { user_id: id })
} else if id <= ZERO_CHANNEL_ID {
let channel_id = -(id + 1_000_000_000_000);
tl::enums::Peer::Channel(tl::types::PeerChannel { channel_id })
} else {
tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: -id })
}
}
fn input_peer_to_peer(ip: tl::enums::InputPeer) -> Result<tl::enums::Peer, crate::InvocationError> {
match ip {
tl::enums::InputPeer::PeerSelf => {
Ok(tl::enums::Peer::User(tl::types::PeerUser { user_id: 0 }))
}
tl::enums::InputPeer::User(u) => Ok(tl::enums::Peer::User(tl::types::PeerUser {
user_id: u.user_id,
})),
tl::enums::InputPeer::Chat(c) => Ok(tl::enums::Peer::Chat(tl::types::PeerChat {
chat_id: c.chat_id,
})),
tl::enums::InputPeer::Channel(c) => Ok(tl::enums::Peer::Channel(tl::types::PeerChannel {
channel_id: c.channel_id,
})),
tl::enums::InputPeer::UserFromMessage(u) => {
Ok(tl::enums::Peer::User(tl::types::PeerUser {
user_id: u.user_id,
}))
}
tl::enums::InputPeer::ChannelFromMessage(c) => {
Ok(tl::enums::Peer::Channel(tl::types::PeerChannel {
channel_id: c.channel_id,
}))
}
tl::enums::InputPeer::Empty => Err(crate::InvocationError::Deserialize(
"cannot resolve InputPeer::Empty".into(),
)),
}
}
impl From<&str> for PeerRef {
fn from(s: &str) -> Self {
normalize_str(s)
}
}
impl From<String> for PeerRef {
fn from(s: String) -> Self {
normalize_str(&s)
}
}
impl From<i64> for PeerRef {
fn from(id: i64) -> Self {
PeerRef::Id(id)
}
}
impl From<i32> for PeerRef {
fn from(id: i32) -> Self {
PeerRef::Id(id as i64)
}
}
impl From<tl::enums::Peer> for PeerRef {
fn from(p: tl::enums::Peer) -> Self {
PeerRef::Peer(p)
}
}
impl From<tl::enums::InputPeer> for PeerRef {
fn from(ip: tl::enums::InputPeer) -> Self {
PeerRef::Input(ip)
}
}
fn normalize_str(s: &str) -> PeerRef {
let s = s.trim();
if let Some(hash) = PeerRef::parse_invite_hash(s) {
return PeerRef::InviteHash(hash.to_owned());
}
if let Some(uname) = parse_tme_username(s) {
return PeerRef::Username(uname.to_owned());
}
if s.starts_with('+') && s.len() > 5 && s[1..].chars().all(|c| c.is_ascii_digit()) {
return PeerRef::Phone(s.to_owned());
}
if let Ok(id) = s.parse::<i64>() {
return PeerRef::Id(id);
}
PeerRef::Username(s.trim_start_matches('@').to_owned())
}
fn parse_tme_username(s: &str) -> Option<&str> {
let path = s
.strip_prefix("https://t.me/")
.or_else(|| s.strip_prefix("http://t.me/"))
.or_else(|| s.strip_prefix("https://telegram.me/"))
.or_else(|| s.strip_prefix("http://telegram.me/"))?;
if path.starts_with('+') || path.starts_with("joinchat/") {
return None;
}
if path.starts_with("c/") {
return None;
}
path.split('/').next().filter(|u| !u.is_empty())
}