From 7b99f4cd24f126834eafc32fa2dc37327f131dd6 Mon Sep 17 00:00:00 2001 From: Mohamed Aouadhi Date: Mon, 5 Feb 2024 11:07:19 +0100 Subject: [PATCH] Add bitcoin command --- src/bot_commands.rs | 13 +++- src/lib/polybot.rs | 1 + src/lib/services/coinmarketcap.rs | 121 ++++++++++++++++++++++++++++++ src/lib/services/mod.rs | 1 + src/lib/types.rs | 1 + src/lib/utils.rs | 21 +++--- 6 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 src/lib/services/coinmarketcap.rs diff --git a/src/bot_commands.rs b/src/bot_commands.rs index 3a611a8..0dec425 100644 --- a/src/bot_commands.rs +++ b/src/bot_commands.rs @@ -4,13 +4,24 @@ use bot_commands_macro::{bot_commands, handler}; pub mod commands { use super::*; + use polybot::services::coinmarketcap::Coinmarket; use polybot::services::llm::{Agent, OpenAiModel}; use polybot::services::openmeteo::OpenMeteo; use polybot::types::{BotUserActions, WeatherProvider}; - use polybot::utils::{get_affirmation, get_ip}; + use polybot::utils::{get_affirmation, get_config, get_ip}; use rand::Rng; use std::io::Cursor; + #[handler(cmd = "/bitcoin")] + async fn get_bitcoin(_user_tx: impl BotUserActions, _: String) -> String { + let config = get_config("config.toml").await.unwrap(); + let market = Coinmarket::new(config.bot.coinmarket_token); + if let Ok(price) = market.get_bitcoin_price().await { + return format!("{:.3} €", price); + } + "Error getting the bitcoin price".to_string() + } + #[handler(cmd = "/ip")] async fn ip(_user_tx: impl BotUserActions, _: String) -> String { if let Ok(ip) = get_ip().await { diff --git a/src/lib/polybot.rs b/src/lib/polybot.rs index 7b96d33..317428d 100644 --- a/src/lib/polybot.rs +++ b/src/lib/polybot.rs @@ -9,6 +9,7 @@ use tokio::select; use tokio::sync::Notify; use tracing::{debug, error, info}; + pub struct Polybot { bot: Arc, config: Config, diff --git a/src/lib/services/coinmarketcap.rs b/src/lib/services/coinmarketcap.rs new file mode 100644 index 0000000..4d65cb7 --- /dev/null +++ b/src/lib/services/coinmarketcap.rs @@ -0,0 +1,121 @@ +use anyhow::Ok; +use chrono::{DateTime, Utc}; +use reqwest::header::{HeaderMap, HeaderValue, ACCEPT}; +use serde::Deserialize; + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct Status { + timestamp: DateTime, + error_code: u32, + error_message: Option, + elapsed: u32, + credit_count: u32, + notice: Option, +} + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct CurrencyQuote { + price: f64, + volume_24h: f64, + volume_24h_reported: f64, + volume_7d: f64, + volume_7d_reported: f64, + volume_30d: f64, + volume_30d_reported: f64, + volume_change_24h: f64, + percent_change_1h: f64, + percent_change_24h: f64, + percent_change_7d: f64, + percent_change_30d: f64, + percent_change_60d: f64, + percent_change_90d: f64, + market_cap: f64, + market_cap_dominance: f64, + fully_diluted_market_cap: f64, + tvl: Option, + last_updated: DateTime, +} + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct Currencies { + #[serde(rename(deserialize = "EUR"))] + currency: CurrencyQuote, +} + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct Data { + id: u32, + name: String, + symbol: String, + slug: String, + date_added: DateTime, + circulating_supply: u32, + infinite_supply: bool, + self_reported_circulating_supply: Option, + self_reported_market_cap: Option, + tvl_ratio: Option, + last_updated: DateTime, + quote: Currencies, +} + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct CoinEntry { + #[serde(rename(deserialize = "BTC"))] + symbol: Vec, +} + +#[allow(unused)] +#[derive(Deserialize, Debug)] +struct BitcoinData { + status: Status, + data: CoinEntry, +} + +/// BitcoinRequest +pub struct Coinmarket { + api_key: String, + client: reqwest::Client, +} + +impl Coinmarket { + pub fn new(api_key: String) -> Self { + Self { + api_key, + client: reqwest::Client::new(), + } + } + + pub async fn get_bitcoin_price(&self) -> anyhow::Result { + let mut headers = HeaderMap::new(); + headers.insert("X-CMC_PRO_API_KEY", HeaderValue::from_str(&self.api_key)?); + headers.insert(ACCEPT, HeaderValue::from_static("application/json")); + + // Define the query parameters + let params = [ + ("symbol", "BTC"), + ("convert", "EUR"), + ("aux", "date_added,circulating_supply,volume_24h_reported,volume_7d,volume_7d_reported,volume_30d,volume_30d_reported"), + ]; + + let resp = self + .client + .get("https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest") + .headers(headers) + .query(¶ms) + .send() + .await? + .text() + .await?; + + let data: BitcoinData = + serde_json::from_str(&resp).expect("problem with getting bitcoin data"); + + tracing::debug!("bitcoin data: {:#?}", data); + Ok(data.data.symbol[0].quote.currency.price) + } +} diff --git a/src/lib/services/mod.rs b/src/lib/services/mod.rs index 089803c..afd29e8 100644 --- a/src/lib/services/mod.rs +++ b/src/lib/services/mod.rs @@ -1,2 +1,3 @@ +pub mod coinmarketcap; pub mod llm; pub mod openmeteo; diff --git a/src/lib/types.rs b/src/lib/types.rs index 257ae58..a39438d 100644 --- a/src/lib/types.rs +++ b/src/lib/types.rs @@ -28,6 +28,7 @@ pub struct BotConfig { pub token: String, pub chat_id: String, pub db_token: String, + pub coinmarket_token: String, } #[derive(Deserialize, Debug, Clone)] diff --git a/src/lib/utils.rs b/src/lib/utils.rs index 0c3ee08..fa97ff4 100644 --- a/src/lib/utils.rs +++ b/src/lib/utils.rs @@ -126,15 +126,18 @@ mod tests { let dir = tempdir().unwrap(); let config_path = dir.path().join("config.toml"); let data = toml::toml! { - [bot] - name = "dummy" - token = "dummytoken" - - [server] - ip = "0.0.0.0" - port = 4443 - privkeyfile = "YOURPRIVATE.key" - pubkeyfile = "YOURPUBLIC.pem" + [bot] + name = "dummy" + token = "tokendummy" + chat_id = "122433" + db_token = "tokendb" + coinmarket_token = "cointoken" + + [server] + ip = "0.0.0.0" + port = 4443 + privkeyfile = "YOURPRIVATE.key" + pubkeyfile = "YOURPUBLIC.pem" }; fs::write(&config_path, data.to_string()).await.unwrap();