├── examples ├── helpers │ ├── mod.rs │ ├── tgfn.rs │ ├── thelp.rs │ └── config.rs ├── simple.rs ├── client_async.rs └── client_event.rs ├── conf └── telegram-client.toml ├── src ├── api │ ├── mod.rs │ ├── api.rs │ └── aevent.rs ├── listener │ ├── mod.rs │ └── listener.rs ├── lib.rs ├── tip.rs ├── rtd.rs ├── errors.rs ├── handler.rs ├── client.rs └── observer.rs ├── .editorconfig ├── .github └── workflows │ ├── develop.yml │ └── publish.yml ├── .gitignore ├── LICENSE ├── Cargo.toml ├── version.md └── README.md /examples/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | pub mod thelp; 4 | pub mod tgfn; 5 | pub mod config; 6 | -------------------------------------------------------------------------------- /conf/telegram-client.toml: -------------------------------------------------------------------------------- 1 | 2 | [log] 3 | type = "console" 4 | path = "tdlib.log" 5 | level = 1 6 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub use self::api::*; 3 | 4 | pub mod aasync; 5 | pub mod aevent; 6 | 7 | mod api; 8 | 9 | -------------------------------------------------------------------------------- /src/listener/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::listener::*; 2 | 3 | pub mod lasync; 4 | pub mod levent; 5 | 6 | mod listener; 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | trim_trailing_whitespace = true 7 | end_of_line = lf 8 | insert_final_newline = true 9 | 10 | indent_size = 2 11 | 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | #[macro_use] 3 | extern crate log; 4 | 5 | #[macro_use] 6 | extern crate lazy_static; 7 | 8 | mod rtd; 9 | mod handler; 10 | mod tip; 11 | mod observer; 12 | 13 | pub mod api; 14 | pub mod client; 15 | pub mod listener; 16 | pub mod errors; 17 | -------------------------------------------------------------------------------- /.github/workflows/develop.yml: -------------------------------------------------------------------------------- 1 | name: Develop 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | name: Build and Test 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Install rust toolchain 12 | uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | override: true 16 | 17 | - name: Run build 18 | run: cargo build 19 | 20 | # todo: test 21 | # - name: Run tests 22 | # run: cargo test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Rust 2 | # Generated by Cargo 3 | # will have compiled files and executables 4 | target 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | .idea 14 | *.iml 15 | 16 | 17 | *.binlog 18 | 19 | lib/ 20 | lib/**/*.so 21 | lib/**/*.so.** 22 | 23 | *.log 24 | tdlib 25 | 26 | 27 | schema/* 28 | .cargo 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | 7 | jobs: 8 | build: 9 | name: Build and Publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Install rust toolchain 15 | uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | 20 | - name: Package 21 | run: cargo package 22 | 23 | - name: Publish 24 | run: | 25 | cargo login ${{ secrets.TOKEN_CRATES_IO }} 26 | cargo publish 27 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | use simple_logger::SimpleLogger; 5 | 6 | use rtdlib::types::*; 7 | use telegram_client::api::Api; 8 | use telegram_client::client::Client; 9 | 10 | fn main() { 11 | SimpleLogger::new() 12 | .with_level(log::LevelFilter::Debug) 13 | .init() 14 | .unwrap(); 15 | 16 | 17 | let api = Api::default(); 18 | let mut client = Client::new(api.clone()); 19 | let listener = client.listener().event_listener_mut(); 20 | 21 | listener.on_receive(|(api, json)| { 22 | debug!("receive {}", json); 23 | Ok(()) 24 | }); 25 | 26 | client.daemon("telegram-rs"); 27 | } 28 | -------------------------------------------------------------------------------- /src/listener/listener.rs: -------------------------------------------------------------------------------- 1 | use crate::listener::lasync::RasyncListener; 2 | use crate::listener::levent::EventListener; 3 | 4 | #[derive(Clone)] 5 | pub struct Listener { 6 | event_listener: EventListener, 7 | rasync_listener: RasyncListener, 8 | } 9 | 10 | 11 | impl Listener { 12 | pub fn new() -> Self { 13 | Self { 14 | event_listener: EventListener::default(), 15 | rasync_listener: RasyncListener::default(), 16 | } 17 | } 18 | } 19 | 20 | impl Listener { 21 | pub fn event_listener(&self) -> &EventListener { 22 | &self.event_listener 23 | } 24 | pub fn rasync_listener(&self) -> &RasyncListener { 25 | &self.rasync_listener 26 | } 27 | pub fn event_listener_mut(&mut self) -> &mut EventListener { 28 | &mut self.event_listener 29 | } 30 | pub fn rasync_listener_mut(&mut self) -> &mut RasyncListener { 31 | &mut self.rasync_listener 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/tip.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use rtdlib::types::RObject; 4 | 5 | const PLZ_POST_ISSUES: &'static str = "PLEASE POST AN ISSUE TO https://github.com/fewensa/telegram-client/issues"; 6 | 7 | const TELEGRAM_DATA_FAIL: &'static str = "TELEGRAM DATA FAIL. IF YOU SEE THIS MESSAGE,"; 8 | 9 | pub fn no_data_returned_from_tdlib() -> &'static str { "No data returned from tdlib" } 10 | 11 | pub fn please_post_issues() -> &'static str { 12 | PLZ_POST_ISSUES 13 | } 14 | 15 | pub fn not_have_listener>(td_name: S) -> String { 16 | format!("NOT HAVE [{}] LISTENER, {} , OR YOU CAN USE `on_receive` TO HANDLE THIS EVENT.", td_name.as_ref(), PLZ_POST_ISSUES) 17 | } 18 | 19 | pub fn data_fail_with_json>(json: S) -> String { 20 | format!("{} {} \n INCLUDE THIS JSON => {}", TELEGRAM_DATA_FAIL, PLZ_POST_ISSUES, json.as_ref()) 21 | } 22 | 23 | pub fn data_fail_with_rtd(robj: ROBJ) -> String { 24 | data_fail_with_json(robj.to_json().unwrap_or("".to_string())) 25 | } 26 | 27 | pub fn un_register_listener>(td_name: S) -> String { 28 | format!("UNREGISTER LISTENER [{}] PLEASE REGISTER IT", td_name.as_ref()) 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice (including the next 13 | paragraph) shall be included in all copies or substantial portions of the 14 | Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 19 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 21 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "telegram-client" 3 | version = "0.8.1" 4 | authors = ["fewensa "] 5 | description = "Telegram client" 6 | homepage = "https://github.com/fewensa/telegram-client" 7 | repository = "https://github.com/fewensa/telegram-client" 8 | license = "MIT" 9 | keywords = ["tdlib", "telegram", "telegram-client", "bindings", "async"] 10 | categories = ["api-bindings"] 11 | include = [ 12 | "Cargo.toml", 13 | "**/*.rs", 14 | "README.md", 15 | "LICENSE" 16 | ] 17 | readme = "README.md" 18 | edition = "2018" 19 | 20 | build = "build.rs" 21 | 22 | [dependencies] 23 | rtdlib = { version = "=0.8.1", features = [ "sys" ] } 24 | #rtdlib = { path = "../rtdlib", features = [ "sys" ] } 25 | #rtdlib = { git = "https://github.com/fewensa/rtdlib.git", features = [ "sys" ] } 26 | 27 | log = "0.4" 28 | regex = "1" 29 | futures = "0.3" 30 | lazy_static = "1.4" 31 | 32 | [dev-dependencies] 33 | colored = "2" 34 | regex = "1" 35 | hostname = "0.1.5" 36 | toml = "0.5.8" 37 | 38 | serde = "1" 39 | serde_derive = "1" 40 | serde_json = "1" 41 | 42 | simple_logger = "1.12" 43 | 44 | toolkit = "0.1" 45 | 46 | tokio = { version = "1.9.0", features = ["full"] } 47 | 48 | 49 | -------------------------------------------------------------------------------- /examples/helpers/tgfn.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | 3 | use rtdlib::types::*; 4 | use telegram_client::api::Api; 5 | 6 | use crate::thelp; 7 | 8 | pub fn type_phone_number(api: &Api) { 9 | let input = thelp::typed(); 10 | api.event_api() 11 | .set_authentication_phone_number( 12 | SetAuthenticationPhoneNumber::builder() 13 | .phone_number(&input) 14 | .build() 15 | ) 16 | .expect("failed to set authentication phone number"); 17 | debug!("Set phone number [{}] {}", input.green(), "(If you copy log to anywhere, don't forget hide your phone number)".red()); 18 | } 19 | 20 | pub fn type_authentication_code(api: &Api) { 21 | let code = thelp::typed(); 22 | api.event_api() 23 | .check_authentication_code(CheckAuthenticationCode::builder().code(&code)) 24 | .expect("failed to check authentication code"); 25 | debug!("Set authentication code: {}", code); 26 | } 27 | 28 | #[allow(dead_code)] 29 | pub fn type_and_register(api: &Api) { 30 | let first_name = thelp::typed_with_message("Please input first name:"); 31 | let last_name = thelp::typed_with_message("Please input last name:"); 32 | debug!("You name is {} {}", first_name, last_name); 33 | api.event_api() 34 | .register_user(RegisterUser::builder() 35 | .first_name(first_name) 36 | .last_name(last_name) 37 | .build()) 38 | .expect("failed to register user"); 39 | } 40 | -------------------------------------------------------------------------------- /version.md: -------------------------------------------------------------------------------- 1 | Version mapping 2 | === 3 | 4 | 5 | - [telegram-client](https://github.com/fewensa/telegram-client) 6 | - [rtdlib](https://github.com/fewensa/rtdlib) 7 | - [td](https://github.com/tdlib/td) 8 | 9 | 10 | The version `1.3`, `1.4`, `1.5`, `1.6`, `1.7` is outdated. the reason you can read 11 | 12 | - [A new telegram client update](https://github.com/fewensa/telegram-client/issues/29) 13 | - [UPDATE_APP_TO_LOGIN](https://github.com/tdlib/td/issues/1758) 14 | 15 | 16 | A fixed version is recommended, you can read [Comparison requirements](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#comparison-requirements) about the fixed version. 17 | Because of cargo's dependency mechanism, if you don't specify a specific version, it will be automatically upgraded, but there is usually a dependency between t and a, and the new version cannot be applied. 18 | The current dependencies are as follows: 19 | 20 | | telegram-client | rtdlib | td | 21 | |--------------------|-------------|-----| 22 | | =0.8.0 | =0.8.0 | [master@fa8feef](https://github.com/tdlib/td/commit/fa8feefed70d64271945e9d5fd010b957d93c8cd) | 23 | | =0.8.1 | =0.8.1 | [master@789b9c0](https://github.com/tdlib/td/commit/789b9c0a554d779945db027fd2612909c676345f) | 24 | | =1.8.0 | =1.8.0 | [v1.8.0](https://github.com/tdlib/td/releases/tag/v1.8.0) | 25 | | =1.8.1 | =1.8.1 | [v1.8.0](https://github.com/tdlib/td/releases/tag/v1.8.0) | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/rtd.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use std::sync::{Arc, Mutex}; 3 | use std::thread; 4 | use std::thread::JoinHandle; 5 | 6 | use crate::api::Api; 7 | use crate::handler::Handler; 8 | use crate::listener::Listener; 9 | 10 | pub struct TdRecv {} 11 | 12 | impl TdRecv { 13 | pub fn new() -> TdRecv { 14 | Self {} 15 | } 16 | 17 | pub fn start( 18 | &self, 19 | api: Arc, 20 | stop_flag: Arc>, 21 | listener: &Listener, 22 | warn_unregister_listener: 23 | Arc, 24 | ) -> JoinHandle<()> { 25 | let event_listener = listener.event_listener(); 26 | let rasync_listener = listener.rasync_listener(); 27 | let event_lout = event_listener.lout(); 28 | let rasync_lout = rasync_listener.lout(); 29 | 30 | thread::spawn(move || { 31 | let is_stop = stop_flag.lock().unwrap(); 32 | 33 | while !*is_stop { 34 | if let Some(json) = api.receive(2.0) { 35 | let api = api.clone(); 36 | let event_lout = event_lout.clone(); 37 | let rasync_lout = rasync_lout.clone(); 38 | let warn_unregister_listener = warn_unregister_listener.clone(); 39 | thread::spawn(move || { 40 | futures::executor::block_on(async move { 41 | Handler::new( 42 | api.as_ref(), 43 | &event_lout, 44 | &rasync_lout, 45 | warn_unregister_listener.borrow(), 46 | ) 47 | .handle(&json) 48 | .await; 49 | }); 50 | }); 51 | } 52 | } 53 | }) 54 | 55 | // thread::spawn(move || futures::executor::block_on(async { 56 | // let is_stop = stop_flag.lock().unwrap(); 57 | // let event_api = api.event_api(); 58 | // while !*is_stop { 59 | // if let Some(json) = api.receive(2.0) { 60 | // Handler::new(&event_api, lout.borrow()).handle(&json).await; 61 | // } 62 | // } 63 | // })) 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | telegram-client 2 | === 3 | 4 | [![Build Status](https://api.travis-ci.org/fewensa/telegram-client.svg)](https://travis-ci.org/fewensa/telegram-client/) 5 | 6 | Telegram client for rust. 7 | 8 | This crate use [td](https://github.com/tdlib/td) to call telegram client api. support async api. 9 | 10 | ## Usage 11 | 12 | ```toml 13 | [dependencies] 14 | telegram-client = "1.8.*" 15 | ``` 16 | 17 | ## version 18 | 19 | Please read: [version](./version.md) 20 | 21 | 22 | ## Note 23 | 24 | Note that you need [tdjson](https://github.com/tdlib/td) dylib file in your path for building and running your application. See also [rtdlib-sys](https://github.com/fewensa/rtdlib-sys) for more details. 25 | 26 | ## Examples 27 | 28 | 29 | ### block 30 | 31 | ```rust 32 | fn main() { 33 | let api = Api::default(); 34 | let mut client = Client::new(api.clone()); 35 | let listener = client.listener(); 36 | 37 | listener.on_receive(|(api, json)| { 38 | debug!("receive {}", json); 39 | Ok(()) 40 | }); 41 | 42 | client.daemon("telegram-rs"); 43 | } 44 | ``` 45 | 46 | ### async 47 | 48 | ```rust 49 | #[tokio::main] 50 | async fn main() { 51 | let api = Api::rasync(); 52 | 53 | let mut client = Client::new(api.api().clone()); 54 | let listener = client.listener(); 55 | 56 | // listener.on_update_authorization_state... 57 | 58 | client.start(); 59 | 60 | let chat = api.get_chat(GetChat::builder().chat_id(1)).await; 61 | println!("{:#?}", chat); 62 | } 63 | ``` 64 | 65 | ### more 66 | 67 | more [examples](./examples) 68 | 69 | ## Event 70 | 71 | Most of the events are from td, two events of particular concern. 72 | 73 | ### on_receive 74 | 75 | This event is receive everything from td, returned data type is a json string. 76 | 77 | ### on_exception 78 | 79 | When td returned json can not deserialize, or your event handler returned error. will be call is event. 80 | 81 | a sample of event handler returned error 82 | 83 | ```rust 84 | listener.on_proxy(|(api, pxy)| { 85 | debug!("Proxy info => {:?}", pxy); 86 | Err(TGError::new("some error")) 87 | }); 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt}; 2 | use std::any::Any; 3 | use std::fmt::Debug; 4 | 5 | use rtdlib::errors::RTDError; 6 | 7 | pub trait TGDatable: Debug { 8 | fn as_any(&self) -> &dyn Any; 9 | } 10 | 11 | #[derive(Debug)] 12 | pub struct TGError { 13 | key: &'static str, 14 | message: Option, 15 | data: Option>, 16 | context: Option> 17 | } 18 | 19 | pub type TGResult = Result; 20 | 21 | impl TGError { 22 | pub fn new(key: &'static str) -> Self { 23 | Self { 24 | key, 25 | message: None, 26 | data: None, 27 | context: None 28 | } 29 | } 30 | pub fn custom(message: impl AsRef) -> Self { 31 | let mut error = Self::new("CUSTOM_ERROR"); 32 | error.set_message(message.as_ref()); 33 | error 34 | } 35 | } 36 | 37 | impl TGError { 38 | pub fn set_key(&mut self, key: &'static str) -> &mut Self { 39 | self.key = key; 40 | self 41 | } 42 | 43 | pub fn set_message(&mut self, message: impl AsRef) -> &mut Self { 44 | self.message = Some(message.as_ref().to_string()); 45 | self 46 | } 47 | 48 | pub fn set_data(&mut self, data: Box) -> &mut Self { 49 | self.data = Some(data); 50 | self 51 | } 52 | 53 | pub fn set_context(&mut self, context: Box) -> &mut Self { 54 | self.context = Some(context); 55 | self 56 | } 57 | 58 | pub fn key(&self) -> &'static str { self.key } 59 | pub fn message(&self) -> &Option { &self.message } 60 | pub fn data(&self) -> &Option> { &self.data } 61 | pub fn context(&self) -> &Option> { &self.context } 62 | } 63 | 64 | 65 | impl fmt::Display for TGError { 66 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 67 | write!(f, "[{}]: {}", self.key, self.message.clone().map_or("".to_string(), |v| v)) 68 | } 69 | } 70 | 71 | impl error::Error for TGError { 72 | fn cause(&self) -> Option<&dyn error::Error> { 73 | None 74 | } 75 | } 76 | 77 | impl From for TGError { 78 | fn from(err: RTDError) -> Self { 79 | let mut tgerr = Self::new("RTDLIB_ERROR"); 80 | tgerr.set_message(err.to_string()); 81 | tgerr.set_context(Box::new(err)); 82 | tgerr 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /examples/helpers/thelp.rs: -------------------------------------------------------------------------------- 1 | use std::{io, thread}; 2 | use std::io::Write; 3 | use std::sync::mpsc; 4 | use std::sync::mpsc::TryRecvError; 5 | use std::time::Duration; 6 | 7 | use colored::Colorize; 8 | 9 | use telegram_client::api::Api; 10 | 11 | use crate::{tgfn, thelp}; 12 | 13 | pub fn typed() -> String { 14 | self::typed_with_message("") 15 | } 16 | 17 | pub fn typed_with_message>(message: S) -> String { 18 | let message = message.as_ref(); 19 | if !message.is_empty() { 20 | self::tip(message); 21 | } 22 | let mut input = String::new(); 23 | match io::stdin().read_line(&mut input) { 24 | Ok(_) => input.trim().to_string(), 25 | Err(e) => panic!("Can not get input value: {:?}", e) 26 | } 27 | } 28 | 29 | #[allow(dead_code)] 30 | pub fn wait_too_many_requests(api: &Api, message: &String) { 31 | thelp::error(&message); 32 | regex::Regex::new("(?P