use chrono::{TimeZone, Utc};
use ferogram::tl;
use ferogram::{Client, TransportKind};
const API_ID: i32 = 0; const API_HASH: &str = ""; const PHONE: &str = "";
const GROUP: &str = "@your_supergroup";
const LIMIT: i32 = 20;
#[tokio::main]
async fn main() {
if let Err(e) = run().await {
eprintln!("error: {e}");
std::process::exit(1);
}
}
async fn run() -> Result<(), Box<dyn std::error::Error>> {
if API_ID == 0
|| API_HASH.is_empty()
|| PHONE.is_empty()
|| GROUP.starts_with('@') && GROUP == "@your_supergroup"
{
eprintln!("Fill in API_ID, API_HASH, PHONE and GROUP at the top of admin_log.rs");
std::process::exit(1);
}
println!("Connecting...");
let (client, _shutdown) = Client::builder()
.api_id(API_ID)
.api_hash(API_HASH)
.transport(TransportKind::Abridged)
.connect()
.await?;
if !client.is_authorized().await? {
login(&client).await?;
client.save_session().await?;
println!("Session saved.");
}
let me = client.get_me().await?;
let display = me
.first_name
.as_deref()
.unwrap_or(me.username.as_deref().unwrap_or("?"));
println!("Logged in as {display}\n");
println!(
"Admin log for {GROUP} (last {LIMIT} events):\n{}",
"-".repeat(60)
);
let events = client.get_admin_log(GROUP, "", LIMIT, 0, 0).await?;
if events.is_empty() {
println!("No events found (log may be empty or you may lack admin access).");
return Ok(());
}
for ev in &events {
let ts = Utc
.timestamp_opt(ev.date as i64, 0)
.single()
.map(|d| d.format("%Y-%m-%d %H:%M:%S UTC").to_string())
.unwrap_or_else(|| format!("unix={}", ev.date));
let action_name = describe_action(&ev.action);
println!(
"[{:>12}] user_id={:<12} {ts} {action_name}",
ev.id, ev.user_id
);
}
println!("{}\nFetched {} event(s).", "-".repeat(60), events.len());
Ok(())
}
fn describe_action(action: &tl::enums::ChannelAdminLogEventAction) -> &'static str {
use tl::enums::ChannelAdminLogEventAction as A;
match action {
A::ParticipantJoin => "joined",
A::ParticipantLeave => "left",
A::ParticipantInvite(_) => "invited a user",
A::ParticipantToggleBan(_) => "banned/unbanned a user",
A::ParticipantToggleAdmin(_) => "changed admin rights",
A::ParticipantJoinByInvite(_) => "joined via invite link",
A::ParticipantJoinByRequest(_) => "approved join request",
A::ParticipantMute(_) => "muted a participant",
A::ParticipantUnmute(_) => "unmuted a participant",
A::ParticipantVolume(_) => "changed participant volume",
A::ParticipantSubExtend(_) => "extended subscription",
A::ChangeTitle(_) => "changed title",
A::ChangeAbout(_) => "changed description",
A::ChangeUsername(_) => "changed username",
A::ChangeUsernames(_) => "changed usernames",
A::ChangePhoto(_) => "changed photo",
A::ChangeStickerSet(_) => "changed sticker set",
A::ChangeEmojiStickerSet(_) => "changed emoji sticker set",
A::ChangeEmojiStatus(_) => "changed emoji status",
A::ChangeLinkedChat(_) => "changed linked chat",
A::ChangeLocation(_) => "changed location",
A::ChangeHistoryTtl(_) => "changed message TTL",
A::ChangeAvailableReactions(_) => "changed reactions",
A::ChangePeerColor(_) => "changed peer color",
A::ChangeProfilePeerColor(_) => "changed profile color",
A::ChangeWallpaper(_) => "changed wallpaper",
A::ToggleInvites(_) => "toggled invites",
A::ToggleSignatures(_) => "toggled signatures",
A::ToggleSignatureProfiles(_) => "toggled signature profiles",
A::TogglePreHistoryHidden(_) => "toggled pre-history visibility",
A::ToggleSlowMode(_) => "toggled slow mode",
A::ToggleGroupCallSetting(_) => "changed group call setting",
A::ToggleNoForwards(_) => "toggled no-forward restriction",
A::ToggleForum(_) => "toggled forum mode",
A::ToggleAntiSpam(_) => "toggled anti-spam",
A::ToggleAutotranslation(_) => "toggled auto-translation",
A::UpdatePinned(_) => "pinned/unpinned message",
A::EditMessage(_) => "edited a message",
A::DeleteMessage(_) => "deleted a message",
A::DefaultBannedRights(_) => "changed default banned rights",
A::StopPoll(_) => "stopped a poll",
A::StartGroupCall(_) => "started group call",
A::DiscardGroupCall(_) => "ended group call",
A::ExportedInviteDelete(_) => "deleted invite link",
A::ExportedInviteRevoke(_) => "revoked invite link",
A::ExportedInviteEdit(_) => "edited invite link",
A::SendMessage(_) => "sent a message",
A::CreateTopic(_) => "created topic",
A::EditTopic(_) => "edited topic",
A::DeleteTopic(_) => "deleted topic",
A::PinTopic(_) => "pinned topic",
A::ParticipantEditRank(_) => "changed participant rank",
}
}
async fn login(client: &Client) -> Result<(), Box<dyn std::error::Error>> {
use ferogram::SignInError;
use std::io::{self, BufRead, Write};
fn prompt(msg: &str) -> io::Result<String> {
print!("{msg}");
io::stdout().flush()?;
let mut line = String::new();
io::stdin().lock().read_line(&mut line)?;
Ok(line.trim().to_string())
}
let token = client.request_login_code(PHONE).await?;
let code = prompt("Enter the code Telegram sent you: ")?;
match client.sign_in(&token, &code).await {
Ok(name) => println!("Signed in as {name}"),
Err(SignInError::PasswordRequired(pw)) => {
let pass = prompt(&format!(
"2FA password (hint: {}): ",
pw.hint().unwrap_or("none")
))?;
client.check_password(*pw, pass.trim()).await?;
}
Err(SignInError::SignUpRequired) => {
eprintln!("Phone not registered on Telegram.");
std::process::exit(1);
}
Err(e) => return Err(e.into()),
}
Ok(())
}