This commit is contained in:
2025-08-26 10:52:28 +04:00
commit c44751a91e
10 changed files with 1612 additions and 0 deletions

2
.env Normal file
View File

@@ -0,0 +1,2 @@
APP_ID="16479456"
APP_HASH="dc30fb72ccb4291c1c3d4b917bfacb05"

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/.direnv

1183
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

15
Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "telegram-exporter"
version = "0.1.0"
edition = "2024"
[dependencies]
dotenvy = "0.15.7"
tdlib-rs = { version = "1.1.0", features = ["pkg-config"] }
tokio = { version = "1.47.1", features = ["full"] }
[build-dependencies]
tdlib-rs = { version = "1.1.0", features = ["pkg-config"] }
[package.metadata.system-deps]
tdjson = "1.8.29"

3
build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tdlib_rs::build::build(None);
}

115
flake.lock generated Normal file
View File

@@ -0,0 +1,115 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1748970125,
"narHash": "sha256-UDyigbDGv8fvs9aS95yzFfOKkEjx1LO3PL3DsKopohA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "323b5746d89e04b22554b061522dfce9e4c49b18",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"process-compose-wrapper": {
"locked": {
"lastModified": 1747144888,
"narHash": "sha256-qxIPqNf4JS9Gz138MP+UOSk7PAsIniDhW0NvOeaC/Ek=",
"ref": "dev",
"rev": "948180a09c429d24648d283212a09ff0f50b2815",
"revCount": 86,
"type": "git",
"url": "https://git.scug.io/nikkuss/process-compose-wrapper.git"
},
"original": {
"ref": "dev",
"type": "git",
"url": "https://git.scug.io/nikkuss/process-compose-wrapper.git"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"process-compose-wrapper": "process-compose-wrapper",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1749091064,
"narHash": "sha256-TGtYjzRX0sueFhwYsnNNFF5TTKnpnloznpIghLzxeXo=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "12419593ce78f2e8e1e89a373c6515885e218acb",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

101
flake.nix Normal file
View File

@@ -0,0 +1,101 @@
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
crane.url = "github:ipetkov/crane";
flake-utils.url = "github:numtide/flake-utils";
process-compose-wrapper = {
url = "git+https://git.scug.io/nikkuss/process-compose-wrapper.git?ref=dev";
};
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
nixpkgs,
crane,
flake-utils,
process-compose-wrapper,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [
(import rust-overlay)
];
};
process-compose = process-compose-wrapper.lib.mkLib pkgs;
inherit (pkgs) lib;
craneLib = (crane.mkLib pkgs).overrideToolchain (
p:
p.rust-bin.nightly.latest.default.override {
extensions = [
"rustc-codegen-cranelift-preview"
"rust-analyzer"
"rust-src"
];
}
);
src = craneLib.cleanCargoSource ./.;
commonArgs = {
inherit src;
strictDeps = true;
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
individualCrateArgs = commonArgs // {
inherit cargoArtifacts;
inherit (craneLib.crateNameFromCargoToml { inherit src; }) version;
};
fileSetForCrate =
crate:
lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./Cargo.toml
./Cargo.lock
];
};
server = craneLib.buildPackage (
individualCrateArgs
// {
}
);
in
{
packages = {
process-compose = process-compose.mkWrapper {
name = "process-compose";
config = (import ./process-compose.nix { inherit pkgs; });
modules = [ ];
};
};
devShells.default = craneLib.devShell {
packages = with pkgs; [
mold
llvmPackages.clang
llvmPackages.lld
watchexec
openssl
pkg-config
tdlib
];
};
}
);
}

10
process-compose.nix Normal file
View File

@@ -0,0 +1,10 @@
{ pkgs, ... }:
{
processes = {
backend = {
command = ''
${pkgs.watchexec}/bin/watchexec --restart -w Cargo.toml -w Cargo.lock -- cargo run
'';
};
};
}

180
src/main.rs Normal file
View File

@@ -0,0 +1,180 @@
use std::{
env,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use tdlib_rs::{
enums::{self, AuthorizationState, Update, User},
functions,
};
use tokio::sync::mpsc::{self, Receiver, Sender};
async fn handle_update(update: Update, auth_tx: &Sender<AuthorizationState>) {
match update {
Update::AuthorizationState(state) => {
auth_tx.send(state.authorization_state).await.unwrap();
}
_ => {
println!("Received update: {:?}", update);
}
}
}
fn ask_user(string: &str) -> String {
println!("{string}");
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}
async fn handle_authorization_state(
client_id: i32,
mut auth_rx: Receiver<AuthorizationState>,
run_flag: Arc<AtomicBool>,
) -> Receiver<AuthorizationState> {
while let Some(state) = auth_rx.recv().await {
match state {
AuthorizationState::WaitTdlibParameters => {
let response = functions::set_tdlib_parameters(
false,
"get_me_db".into(),
String::new(),
String::new(),
false,
false,
false,
false,
env::var("API_ID").unwrap().parse().unwrap(),
env::var("API_HASH").unwrap().into(),
"en".into(),
"Desktop".into(),
String::new(),
env!("CARGO_PKG_VERSION").into(),
client_id,
)
.await;
if let Err(error) = response {
println!("{}", error.message);
}
}
AuthorizationState::WaitPhoneNumber => loop {
let input = ask_user("Enter your phone number (include the country calling code):");
let response =
functions::set_authentication_phone_number(input, None, client_id).await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
},
AuthorizationState::WaitOtherDeviceConfirmation(x) => {
println!(
"Please confirm this login link on another device: {}",
x.link
);
}
AuthorizationState::WaitEmailAddress(_x) => {
let email_address = ask_user("Please enter email address: ");
let response =
functions::set_authentication_email_address(email_address, client_id).await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
}
AuthorizationState::WaitEmailCode(_x) => {
let code = ask_user("Please enter email authentication code: ");
let response = functions::check_authentication_email_code(
enums::EmailAddressAuthentication::Code(
tdlib_rs::types::EmailAddressAuthenticationCode { code },
),
client_id,
)
.await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
}
AuthorizationState::WaitCode(_) => loop {
let input = ask_user("Enter the verification code:");
let response = functions::check_authentication_code(input, client_id).await;
match response {
Ok(_) => break,
Err(e) => println!("{}", e.message),
}
},
AuthorizationState::WaitRegistration(_x) => {
// x useless but contains the TOS if we want to show it
let first_name = ask_user("Please enter your first name: ");
let last_name = ask_user("Please enter your last name: ");
functions::register_user(first_name, last_name, false, client_id)
.await
.unwrap();
}
AuthorizationState::WaitPassword(_x) => {
let password = ask_user("Please enter password: ");
functions::check_authentication_password(password, client_id)
.await
.unwrap();
}
AuthorizationState::Ready => {
break;
}
AuthorizationState::Closed => {
// Set the flag to false to stop receiving updates from the
// spawned task
run_flag.store(false, Ordering::Release);
break;
}
_ => (),
}
}
auth_rx
}
#[tokio::main]
async fn main() {
dotenvy::dotenv().ok();
let client_id = tdlib_rs::create_client();
let (auth_tx, auth_rx) = mpsc::channel(5);
let run_flag = Arc::new(AtomicBool::new(true));
let run_flag_clone = run_flag.clone();
let handle = tokio::spawn(async move {
while run_flag_clone.load(std::sync::atomic::Ordering::Relaxed) {
let result = tokio::task::spawn_blocking(tdlib_rs::receive)
.await
.unwrap();
if let Some((update, _client_id)) = result {
handle_update(update, &auth_tx).await;
} else {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
}
});
functions::set_log_verbosity_level(2, client_id)
.await
.unwrap();
// Handle the authorization state to authenticate the client
let auth_rx = handle_authorization_state(client_id, auth_rx, run_flag.clone()).await;
// Run the get_me() method to get user information
let User::User(me) = functions::get_me(client_id).await.unwrap();
println!("Hi, I'm {}", me.first_name);
// Tell the client to close
functions::close(client_id).await.unwrap();
// Handle the authorization state to wait for the "Closed" state
handle_authorization_state(client_id, auth_rx, run_flag.clone()).await;
// Wait for the previously spawned task to end the execution
handle.await.unwrap();
}