1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#![feature(async_await)]
#[macro_use]
extern crate derive_builder;
#[macro_use]
extern crate serde_derive;

use std::{error, fmt};
use std::sync::Arc;

use futures::{Future, FutureExt, SinkExt, TryFutureExt};
use futures::channel::mpsc::Receiver;
use futures::compat::Future01CompatExt;
use reqwest::r#async::{Client, Response};
use serde::Deserialize;

use types::*;

pub mod types;
pub mod methods;


#[derive(Debug)]
pub enum BotError {
    Http(reqwest::Error),
    Json(serde_json::error::Error),
    Api {
        error_code: i64,
        description: String,
        parameters: Option<ResponseParameters>,
    },
}

impl From<serde_json::error::Error> for BotError {
    fn from(err: serde_json::error::Error) -> BotError {
        BotError::Json(err)
    }
}

impl From<reqwest::Error> for BotError {
    fn from(err: reqwest::Error) -> BotError {
        BotError::Http(err)
    }
}

impl fmt::Display for BotError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            BotError::Http(ref e) => write!(f, "{}", e),
            BotError::Json(ref e) => write!(f, "{}", e),
            BotError::Api {
                error_code,
                ref description,
                ..
            } => write!(f, "Error {0}: {1}", error_code, description),
        }
    }
}

impl error::Error for BotError {
    fn description(&self) -> &str {
        "Something unexpected occured while talking to the telegram bot api." // meh
    }
}

pub trait TgMethod {
    type ResponseType;
    const PATH: &'static str;
}


pub trait Captures<'a> {}

impl<'a, T> Captures<'a> for T {}


#[derive(Debug, Clone)]
pub struct Bot {
    token: String,
    client: Arc<Client>,
}

impl Bot {
    pub fn new(bot_token: impl Into<String>) -> Bot {
        Bot {
            token: bot_token.into(),
            client: Arc::new(Client::new()),
        }
    }

    pub fn send<'a: 'c, 'b: 'c, 'c, R: for<'de> Deserialize<'de>, M: TgMethod<ResponseType=R> + serde::Serialize>(
        &'a self, m: &'b M,
    ) -> impl Future<Output=Result<R, BotError>> + Captures<'a> + Captures<'b> + 'c {
        async move {
            let url = format!("https://api.telegram.org/bot{}/{}", self.token, M::PATH);

            let mut resp: Response = self.client.post(&url).json(m).send().compat().await.unwrap();
            let res: ApiResult<R> = resp.json().compat().await?;

            res.into()
        }
    }

    pub fn start_polling(&self) -> Receiver<Update> {
        let (mut tx, rx) = futures::channel::mpsc::channel(100);

        let bot = self.clone();

        tokio::spawn(async move {
            let mut req = methods::GetUpdates {
                offset: Some(0),
                limit: None,
                timeout: Some(5 * 60),
                allowed_updates: None,
            };

            loop {
                let updates = bot.send(&req).await;

                for update in updates.unwrap() {
                    req.offset = Some(update.update_id + 1);
                    tx.send(update).await.unwrap();
                }
            }
        }.boxed().unit_error().compat());

        rx
    }
}