telegram-client

Telegram client for rust.

This crate use libtdjson call telegram client api, libtdjson create is rtdlib

Notice

Currently, not all features are available. about how to support it, see Advanced

Usage

toml telegram-client="0.2"

Note that you need libtdjson.so.1.4.0 in your path for building and running your application. See also rtdlib for more details.

Examples

```rust fn main() { let api = Api::default(); let mut client = Client::new(api.clone()); let listener = client.listener();

listener.onreceive(|(api, object)| { println!("receive {:?} => {}", object.tdtype(), object.to_json()); });

client.daemon("telegram-rs"); } ```

more examples

Panic

If you see the code, in types, let see TGMessageForwardInfo

rust impl TGMessageForwardInfo { pub fn from_message_id(&self) -> i64 { self.td_origin().from_message_id().map(|v| v).expect(&errors::data_fail_with_rtd(self.td_origin())[..]) } }

When json can not parse to TGFile, will panic progress.

Why it's? instead of returning a Result to you?

Yes, maybe returning Result is best, won't let program exit. But this will be complicated by the code.

First of all to know what causes this error.

  1. td returned fail json.
  2. telegram-client crate covert fail.

The first you need create issue to td

The second, only from_message_id field is Option field, check the documentation to see if some are optional field, and update it. When telegram-client is stable, In principle, no errors will occur unless td is changed

Advanced

Because telegram client has a lot of events, this crate may not contain all the event handling code. If found will print a WARN level logger. Guide you to submit an issue.

In addition, you have at least two ways to solve this problem.

on_receive

When you get listener from Client, you can use listener add on_receive to handle any data from tdlib.

```rust fn main() { let mut client = Client::default(); let listener = client.listener();

listener.onreceive(|(api: &Api, object: &Box)| { let tdtype: rtdlib::types::RTDType = object.tdtype(); match tdtype { rtdlib::types::RTDType::UpdateUser => { rtdlib::types::UpdateUser::fromjson(object.tojson()) .map(|update_user: rtdlib::types::UpdateUser| { // do some thing }); } _ => {} } }); } ```

RTDType is an enum, include all tdlib types. and on_receive as long as the callback is called after receiving the data.

Then combine rtdtype and receive to handle all events

custom event

The code for this crate is designed to be configurable. You can check build/tpl directory to see the configuration file.

gen_listener

Let's take a look at the listener_rs.tpl.toml file.

```toml [info]

uses = [ "rtdlib::types as tdtypes", "crate::types as tgtypes", "crate::api::Api", ]

commentlistener = "Telegram client event listener" commentlout = "Get listener"

tt: listen handle type

mapper: map tdlib type

comment: listenr comment

[lin]

receive = { tt = { object = "Box" }, comment = "when receive data from tdlib" }

[lin.option] tdtype = "updateOption" tt = { namespace = "tgtypes", object = "TGUpdateOption"} comment = "An option changed its value."

[lin.authorizationstate] tdtype = "updateAuthorizationState" tt = { namespace = "tg_types", object = "TGAuthorizationState" } comment = "The user authorization state has changed." ```

This config will be generated src/listener.rs and src/handler/handler_receive.rs file.

Listen.rs will provide the user with the registration of the event,

Generated src/listener.rs like this:

```rust use std::sync::Arc;

use rtdlib::types as tdtypes; use crate::types as tgtypes; use crate::api::Api;

/// Telegram client event listener

[derive(Clone)]

pub struct Listener { lauthorizationstate: Option>, l_option: Option>, }

impl Listener { pub fn new() -> Self { Self { lauthorizationstate: None, l_option: None, } }

/// when receive data from tdlib pub fn onreceive(&mut self, fnc: F) -> &mut Self where F: Fn((&Api, &Box)) + Send + Sync + 'static { self.lreceive = Some(Arc::new(fnc)); self } /// The user authorization state has changed. pub fn onauthorizationstate(&mut self, fnc: F) -> &mut Self where F: Fn((&Api, &tgtypes::TGAuthorizationState)) + Send + Sync + 'static { self.lauthorizationstate = Some(Arc::new(fnc)); self } /// An option changed its value. pub fn onoption(&mut self, fnc: F) -> &mut Self where F: Fn((&Api, &tgtypes::TGUpdateOption)) + Send + Sync + 'static { self.loption = Some(Arc::new(fnc)); self } } ```

And then you can use this listener

```rust fn main() { let api = Api::default(); let mut client = Client::new(api.clone()); let listener = client.listener();

listener.onoption(|(api, option)| { let value = option.value(); if value.isempty() { debug!(exmlog::examples(), "Receive an option {} but it's empty", option.name()) } if value.isstring() { debug!(exmlog::examples(), "Receive an option {}: String => {}", option.name(), value.asstring().mapor("None".tostring(), |v| v)) } if value.isinteger() { debug!(exmlog::examples(), "Receive an option {}: i32 => {}", option.name(), value.asinteger().mapor(-1, |v| v)) } if value.isbool() { debug!(exmlog::examples(), "Receive an option {}: bool => {}", option.name(), value.asbool().mapor(false, |v| v)) }

option.on_name("version", |value| {
  value.as_string().map(|v| { debug!(exmlog::examples(), "VERSION IS {}", v); });
});

});

client.daemon("telegram-rs"); } ```

gen_types

rtdtype has many primitive types, if want to use it better, you can remodel it with gen_types.

```toml [tmod]

uses = [ "self::fupdateoption::TGOptionValue", "self::fmessagecontent::*", ]

[[tmod.mods]] name = "tgmacro" macrouse = true

[tgypes]

if typen not set, default is TG$inner

[[tgypes.update_option]] uses = [] typen = "TGUpdateOption" inner = "UpdateOption" comment = "An option changed its value."

[[tgypes.message_content]] inner = "VoiceNote"

[[tgypes.message_content]] inner = "Venue" ```

You can edit tg_types.tpl.toml, tmod will generate the mod.rs file, tgypes will generate t_*.rs and f_*.rs.

t_*.rs will be rebuilt each time, f_*.rs will only be built once, f_*.rs is suitable for writing supplementary code.

The above configuration will generate the following code

src/types/mod.rs

```rust pub use self::tupdateoption::; pub use self::f_update_option::TGOptionValue; pub use self::t_message_content::; pub use self::fmessagecontent::*;

[macrouse] mod tgmacro;

mod tupdateoption; mod fupdateoption; mod tmessagecontent; mod fmessagecontent; ```

src/types/tupdateoption.rs

```rust use rtdlib::types::*; use serde::{Serialize, Serializer, Deserialize, Deserializer}; use rtdlib::types::{RObject, RTDType};

/// An option changed its value.

[derive(Debug, Clone)]

pub struct TGUpdateOption { inner: UpdateOption }

impl RObject for TGUpdateOption { fn tdname(&self) -> &'static str { self.inner.tdname() }

fn tdtype(&self) -> RTDType { self.inner.tdtype() }

fn tojson(&self) -> String { self.inner.tojson() } }

impl Serialize for TGUpdateOption { fn serialize(&self, serializer: S) -> Result where S: Serializer { self.inner.serialize(serializer) } }

impl<'de> Deserialize<'de> for TGUpdateOption { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { UpdateOption::deserialize(deserializer).map(|inner| TGUpdateOption::new(inner)) } }

impl TGUpdateOption { pub fn new(inner: UpdateOption) -> Self { Self { inner } }

pub fn fromjson>(json: S) -> Option { UpdateOption::fromjson(json).map(|inner| TGUpdateOption::new(inner)) }

pub fn td_origin(&self) -> &UpdateOption { &self.inner } } ```

src/types/tmessagecontent.rs

```rust

[derive(Debug, Clone)]

pub struct TGVoiceNote { inner: VoiceNote }

impl RObject for TGVoiceNote { fn tdname(&self) -> &'static str { self.inner.tdname() }

fn tdtype(&self) -> RTDType { self.inner.tdtype() }

fn tojson(&self) -> String { self.inner.tojson() } }

impl Serialize for TGVoiceNote { fn serialize(&self, serializer: S) -> Result where S: Serializer { self.inner.serialize(serializer) } }

impl<'de> Deserialize<'de> for TGVoiceNote { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { VoiceNote::deserialize(deserializer).map(|inner| TGVoiceNote::new(inner)) } }

impl TGVoiceNote { pub fn new(inner: VoiceNote) -> Self { Self { inner } }

pub fn fromjson>(json: S) -> Option { VoiceNote::fromjson(json).map(|inner| TGVoiceNote::new(inner)) }

pub fn td_origin(&self) -> &VoiceNote { &self.inner } }

[derive(Debug, Clone)]

pub struct TGVenue { inner: Venue }

impl RObject for TGVenue { fn tdname(&self) -> &'static str { self.inner.tdname() }

fn tdtype(&self) -> RTDType { self.inner.tdtype() }

fn tojson(&self) -> String { self.inner.tojson() } }

impl Serialize for TGVenue { fn serialize(&self, serializer: S) -> Result where S: Serializer { self.inner.serialize(serializer) } }

impl<'de> Deserialize<'de> for TGVenue { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { Venue::deserialize(deserializer).map(|inner| TGVenue::new(inner)) } }

impl TGVenue { pub fn new(inner: Venue) -> Self { Self { inner } }

pub fn fromjson>(json: S) -> Option { Venue::fromjson(json).map(|inner| TGVenue::new(inner)) }

pub fn td_origin(&self) -> &Venue { &self.inner } } ```

The generated code is like this. Next, you can supplement the generated code.

src/types/fupdateoptions.rs

```rust use crate::types::TGUpdateOption; use rtdlib::types as td_type; use crate::errors;

impl TGUpdateOption {

pub fn name(&self) -> String { self.tdorigin().name().expect(&errors::datafailwithrtd(self.td_origin())[..]) }

pub fn value(&self) -> TGOptionValue { TGOptionValue::new(self.td_origin().value()) }

pub fn onname, F: FnOnce(&TGOptionValue)>(&self, name: S, fnc: F) { let value = TGOptionValue::new(self.tdorigin().value()); if &self.name()[..] == name.asref() && value.issome() { fnc(&value) } }

}

pub struct TGOptionValue { value: Option> }

macrorules! optionvalueas { ($valueclass:ident, $retype:tt) => ( fn ovas(value: &Option>) -> Option<$retype> { value.clone().filter(|v| v.tdtype() == tdtype::RTDType::$valueclass) .map(|v| tdtype::$valueclass::fromjson(v.tojson())) .filter(|v| v.issome()) .map(|v| v.map(|v| v.value().clone().map(|v| v))) .mapor(None, |v| v) .mapor(None, |v| v) } ) }

impl TGOptionValue {

fn new(value: Option>) -> Self { Self { value } }

fn issome(&self) -> bool { self.value.issome() }

pub fn isbool(&self) -> bool { self.value.clone().map(|v| v.tdtype() == tdtype::RTDType::OptionValueBoolean) .mapor(false, |v| v) }

pub fn isempty(&self) -> bool { self.value.clone().map(|v| v.tdtype() == tdtype::RTDType::OptionValueEmpty) .mapor(false, |v| v) }

pub fn isstring(&self) -> bool { self.value.clone().map(|v| v.tdtype() == tdtype::RTDType::OptionValueString) .mapor(false, |v| v) }

pub fn isinteger(&self) -> bool { self.value.clone().map(|v| v.tdtype() == tdtype::RTDType::OptionValueInteger) .mapor(false, |v| v) }

pub fn asstring(&self) -> Option { optionvalue_as!(OptionValueString, String); ovas(&self.value) }

pub fn asinteger(&self) -> Option { optionvalue_as!(OptionValueInteger, i32); ovas(&self.value) }

pub fn asbool(&self) -> Option { optionvalue_as!(OptionValueBoolean, bool); ovas(&self.value) }

} ```