use frakti::{
AsyncTelegramApi,
client_cyper::Bot,
methods::{GetChatMemberParams, GetChatParams},
types::{Birthdate, ChatFullInfo, ChatMember, ChatType, User},
};
use log::{error, warn};
use std::fmt::Write;
pub fn dox(doxee: &User, full_info: Option<&ChatFullInfo>) -> String {
let mut report = String::new();
let id = doxee.id;
_ = write!(report, "您好,请问是用户 ID 为 <code>{id}</code>")
.inspect_err(|e| error!("Cannot write to report: {e}"));
if let Some(username) = &doxee.username {
_ = write!(report, ",用户名为 <code>@{username}</code>")
.inspect_err(|e| error!("Cannot write to report: {e}"));
}
if let Some(full_info) = full_info
&& let Some(detail) = detailed_doxing(full_info)
{
report.push_str(&detail);
}
report.push_str(" 的 <code>");
let first_name = &doxee.first_name;
report.push_str(&escape(first_name));
if let Some(last_name) = &doxee.last_name {
report.push(' ');
report.push_str(&escape(last_name));
}
report.push_str("</code> ");
if fish_cake(&doxee) {
report.push_str("南梁");
} else if doxee.is_premium == Some(true) {
report.push_str("富哥");
} else {
report.push_str("先生");
}
report.push_str("吗?");
report
}
fn detailed_doxing(full_info: &ChatFullInfo) -> Option<String> {
let user_id = full_info.id;
let mut detail = String::new();
if !matches!(full_info.type_field, ChatType::Private) {
warn!("Trying to dox a non-private chat: {user_id}");
return None;
}
if let Some(birthday) = &full_info.birthdate {
let Birthdate { year, month, day } = birthday;
_ = match year {
None => write!(detail, ",生日在 {month:02} 月 {day:02} 日"),
Some(year) => write!(detail, ",生日在 {year:04} 年 {month:02} 月 {day:02} 日"),
}
.inspect_err(|e| error!("Cannot write to detail: {e}"));
}
if let Some(channel) = &full_info.personal_chat {
if let Some(channel_username) = &channel.username {
_ = write!(detail, ",开通了 tg 空间 @{channel_username}")
.inspect_err(|e| warn!("Cannot write to detail: {e}"));
} else {
warn!("Cannot get username of personal channel: {}", channel.id);
}
}
Some(detail)
}
pub async fn get_full_info(bot: &Bot, user_id: u64) -> Option<ChatFullInfo> {
let chat_id = match i64::try_from(user_id) {
Ok(id) => id,
Err(e) => {
warn!("[get_full_info] Cannot convert user_id {user_id} to chat_id: {e:?}");
return None;
}
};
let get_params = GetChatParams::builder().chat_id(chat_id).build();
match bot.get_chat(&get_params).await {
Err(e) => {
warn!("Error querying {user_id}: {e:?}");
None
}
Ok(r) => Some(r.result),
}
}
fn escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
}
pub async fn get_user_full(bot: &Bot, user_id: u64) -> Option<(User, Option<ChatFullInfo>)> {
let user = get_user_by_id(bot, user_id).await?;
Some((user, get_full_info(bot, user_id).await))
}
async fn get_user_by_id(bot: &Bot, user_id: u64) -> Option<User> {
let chat_id = match i64::try_from(user_id) {
Ok(id) => id,
Err(e) => {
warn!("[get_user_by_id] Cannot convert user_id {user_id} to chat_id: {e:?}");
return None;
}
};
let get_params = GetChatMemberParams::builder()
.chat_id(chat_id)
.user_id(user_id)
.build();
match bot.get_chat_member(&get_params).await {
Ok(result) => {
let user = match result.result {
ChatMember::Administrator(admin) => admin.user,
ChatMember::Creator(creator) => creator.user,
ChatMember::Kicked(kicked) => kicked.user,
ChatMember::Left(left) => left.user,
ChatMember::Member(member) => member.user,
ChatMember::Restricted(restricted) => restricted.user,
};
Some(user)
}
Err(e) => {
warn!("Cannot get user from id {user_id}: {e:?}");
None
}
}
}
#[allow(dead_code, reason = "Kept for reference")]
async fn get_user_full_by_username(
bot: &Bot,
username: String,
) -> Option<(User, Option<ChatFullInfo>)> {
let get_params = GetChatParams::builder()
.chat_id(format!("@{username}"))
.build();
let full_info = match bot.get_chat(&get_params).await {
Err(e) => {
warn!("Error querying @{username}: {e:?}");
None
}
Ok(r) => Some(r.result),
}?;
if !matches!(full_info.type_field, ChatType::Private) {
warn!("Trying to get user full on a non-private chat: @{username}");
return None;
}
let chat_id = full_info.id;
let user_id = match u64::try_from(chat_id) {
Ok(id) => id,
Err(e) => {
warn!("[get_user_full_by_username] Cannot convert chat_id {chat_id} to user_id: {e:?}");
return None;
}
};
let Some(user) = get_user_by_id(bot, user_id).await else {
error!("Cannot get user by id, even we've got chat full info");
return None;
};
Some((user, Some(full_info)))
}
fn fish_cake(user: &User) -> bool {
fn has_fish_cake(s: &String) -> bool {
s.find('🍥').is_some() || s.find("🏳️⚧️").is_some()
}
has_fish_cake(&user.first_name)
|| user
.last_name
.as_ref()
.map(has_fish_cake)
.unwrap_or_default()
}