├── media └── logo.png ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.md │ ├── feature_request.md │ ├── parse-error.md │ └── unknown-telegram-error.md ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── CONTRIBUTING.md ├── src ├── examples │ └── bot.rs ├── utils.rs ├── errors.rs ├── types.rs ├── lib.rs ├── tests.rs └── bot.rs ├── LICENSE ├── example.rs ├── README.md ├── CODE_STYLE.md └── Cargo.lock /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkangc/rustygram/HEAD/media/logo.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: rustygram Discussions 3 | url: https://github.com/ExtremelySunnyYk/rustygram/discussions/ 4 | about: Please ask and answer questions here. 5 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | I tried this code: 10 | 11 | ```rust 12 | 13 | ``` 14 | 15 | I expected to see this happen: _explanation_ 16 | 17 | Instead, this happened: _explanation_ 18 | 19 | ## Meta 20 | 21 | - `rustygram` version: 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # swap files from vim 13 | *.swp 14 | *.swo 15 | 16 | .env 17 | 18 | 19 | .vscode/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Feature Request: ' 5 | labels: feature-request 6 | assignees: Hirrolot, WaffleLapkin 7 | 8 | --- 9 | 10 | 11 | 12 | ### Pros 13 | 14 | 15 | 16 | ### Cons 17 | 18 | 19 | 20 | ### Alternatives 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/parse-error.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Parse error 3 | about: Report issue with `rustygram` parsing of telegram response 4 | title: "Parse Error: " 5 | labels: bug, FIXME, core 6 | assignees: WaffleLapkin 7 | --- 8 | 9 | When using `<...>` method I've got `RequestError::InvalidJson` error with the following description: 10 | 11 | ```text 12 | 13 | ``` 14 | 15 | ## Steps to reproduce 16 | 17 | 18 | 19 | ## Meta 20 | 21 | - `rustygram` version: 22 | 23 | ### Additional context 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/unknown-telegram-error.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Unknown telegram error 3 | about: You've found telegram error which is not known to rustygram 4 | title: "Unknown Error: " 5 | labels: bug, good first issue, FIXME, core, Unknown API error 6 | assignees: "" 7 | --- 8 | 9 | When using `<...>` method I've got `ApiError::Unknown` error with the following description: 10 | 11 | ```text 12 | 13 | ``` 14 | 15 | ## Steps to reproduce 16 | 17 | 18 | 19 | ## Meta 20 | 21 | - `rustygram` version: 22 | 23 | ### Additional context 24 | 25 | 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustygram" 3 | version = "0.1.4" 4 | edition = "2021" 5 | authors = ["Yong Kang Chia "] 6 | description = "RustyGram is a minimal and blazing fast telegram notification framework for Rust" 7 | repository = "https://github.com/ExtremelySunnyYK/rustygram" 8 | keywords = ["telegram", "bot", "api", "notification", "trading"] 9 | categories = ["api-bindings", "asynchronous"] 10 | readme = "README.md" 11 | license = "MIT" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | reqwest = {version = "0.11.22", features=["json", "multipart"]} 17 | serde_json = "1.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | dotenv = "0.15.0" 20 | tokio = { version = "1.34.0", features = ["macros"] } 21 | tempfile = "3.10.1" 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Before contributing, please read [our code style](https://github.com/ExtremelySunnyYK/rustygram/blob/master/CODE_STYLE.md) and [the license](https://github.com/ExtremelySunnyYK/rustygram/blob/master/LICENSE). 4 | 5 | To change the source code, fork the `master` branch of this repository and work inside your own branch. Then send us a PR into `master` branch and wait for the CI to check everything. However, you'd better check changes first locally: 6 | 7 | ``` 8 | cargo clippy --all --all-features --all-targets 9 | cargo test --all 10 | RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features 11 | # Using nightly rustfmt 12 | cargo +nightly fmt --all -- --check 13 | ``` 14 | 15 | To report a bug, suggest new functionality, or ask a question, go to [Issues](https://github.com/ExtremelySunnyYK/rustygram/issues). Try to make MRE (**M**inimal **R**eproducible **E**xample) and specify your rustygram version to let others help you. 16 | -------------------------------------------------------------------------------- /src/examples/bot.rs: -------------------------------------------------------------------------------- 1 | /// Create a new bot using the Telegram bot token and chat ID from the environment variables. 2 | /// 3 | /// # Example 4 | /// 5 | /// ``` 6 | /// use rustygram::bot::Bot; 7 | /// 8 | /// # fn main() -> Result<(), Box> { 9 | /// // Assuming the environment variables are set 10 | /// let bot = create_bot()?; 11 | /// // Use the bot... 12 | /// # Ok(()) 13 | /// # } 14 | /// ``` 15 | fn create_bot() -> Bot { 16 | dotenv().ok(); 17 | let token = env::var("TELEGRAM_BOT_TOKEN").expect("TELEGRAM_BOT_TOKEN not set"); 18 | let chat_id = env::var("TELEGRAM_CHAT_ID").expect("TELEGRAM_CHAT_ID not set"); 19 | Bot::new(token, chat_id) 20 | } 21 | 22 | #[tokio::main] 23 | /// Send a message to the chat associated with the bot. 24 | fn send_message() -> Result<(), ErrorResult> { 25 | let bot = create_bot(); 26 | let msg = "Hello, world!"; 27 | let options = None; 28 | bot.send_message(msg, options) 29 | } 30 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | errors::ErrorResult, 3 | types::{SendMessageParseMode, StatusCode}, 4 | }; 5 | 6 | /// Get a string representing of specified parse mode. 7 | /// 8 | /// This function returns static string as it is expected to be used across 9 | /// the lifetime of the application. So it has no need to return new instance 10 | /// of `String` every time. 11 | /// 12 | /// # Arguments 13 | /// * `mode` - parse mode 14 | pub fn get_send_message_parse_mode_str(mode: &SendMessageParseMode) -> &'static str { 15 | match mode { 16 | SendMessageParseMode::MarkdownV2 => "MarkdownV2", 17 | SendMessageParseMode::HTML => "HTML", 18 | } 19 | } 20 | 21 | /// Create an `ErrorResult` from input of string slice. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// * `code` - `StatusCode` 26 | /// * `msg` - message string to add as an error description 27 | pub fn create_error_result_str(code: StatusCode, msg: &str) -> Result<(), ErrorResult> { 28 | Err(ErrorResult { 29 | code: code as u16, 30 | msg: msg.to_string(), 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 rustygram 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 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Error, Formatter}; 2 | 3 | use reqwest::{multipart, StatusCode}; 4 | /// ErrorResult usually returned to indicate result from calling APIs related 5 | /// functions. 6 | #[derive(Debug)] 7 | pub struct ErrorResult { 8 | pub code: u16, // error returned code 9 | pub msg: String, // error string description 10 | } 11 | 12 | /// Telegram's error result. 13 | /// In case of error occurred as part of telegram API calling, then this struct 14 | /// will be formed and returned. 15 | #[derive(Debug, serde::Deserialize)] 16 | pub struct TelegramErrorResult { 17 | pub ok: bool, 18 | pub error_code: i32, 19 | pub description: String, 20 | } 21 | 22 | impl Display for ErrorResult { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 24 | write!(f, "{}", self.msg) 25 | } 26 | } 27 | impl From for ErrorResult { 28 | fn from(err: std::io::Error) -> Self { 29 | ErrorResult { 30 | code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(), 31 | msg: format!("IO Error: {}", err), 32 | } 33 | } 34 | } 35 | 36 | impl From for ErrorResult { 37 | fn from(err: reqwest::Error) -> Self { 38 | ErrorResult { 39 | code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(), 40 | msg: format!("HTTP Request Error: {}", err), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | /// Parse mode for `sendMessage` API 2 | pub enum SendMessageParseMode { 3 | /// MarkdownV2 style 4 | MarkdownV2, 5 | 6 | /// HTML style 7 | HTML, 8 | } 9 | 10 | /// Options which can be used with `sendMessage` API 11 | pub struct SendMessageOption { 12 | /// Parse mode 13 | pub parse_mode: Option, 14 | } 15 | 16 | /// Status code indicating the result of APIs related function call. 17 | #[derive(Debug, Clone, Copy)] 18 | pub enum StatusCode { 19 | /// Success 20 | Success = 0, 21 | 22 | /// Internal error due to various internal operations. 23 | /// Whenever Telegram's related operations occurred with error, then this 24 | /// value will be used. 25 | ErrorInternalError, 26 | } 27 | 28 | impl StatusCode { 29 | /// Get the status code as u16. 30 | pub fn as_u16(&self) -> u16 { 31 | *self as u16 32 | } 33 | } 34 | 35 | /// Request Object for `sendMessage` API 36 | /// See 37 | /// NOTE: serde::Serialize can work with &str 38 | #[derive(Debug, serde::Serialize)] 39 | pub struct RequestObj { 40 | chat_id: String, 41 | text: String, 42 | // this is required unfortunately, see https://github.com/serde-rs/serde/issues/947 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | parse_mode: Option, 45 | } 46 | 47 | impl RequestObj { 48 | /// Create a new `RequestObj` with given chat id and message. 49 | pub fn new(chat_id: &str, text: &str, parse_mode: Option) -> Self { 50 | Self { 51 | chat_id: chat_id.to_owned(), 52 | text: text.to_owned(), 53 | parse_mode, 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | /// High Level Wrapper API of Bot. 4 | /// 5 | /// This module provides a high-level API for interacting with Telegram's bot API. It provides 6 | /// functionality for creating a bot with a token and chat ID, and for sending messages. 7 | /// 8 | /// The `create_bot` function is used to create a new `Bot` instance. The `bot_token` parameter 9 | /// should be the token provided by the BotFather on Telegram, and `chat_id` should be the ID 10 | /// of the chat where the bot should send messages. 11 | /// 12 | /// The `send_message` function is used to send a message to the chat associated with the `Bot`. 13 | /// The `msg` parameter is the text of the message to send, and the `options` parameter can be 14 | /// used to specify additional options like parse mode. 15 | use bot::Bot; 16 | pub mod bot; 17 | pub mod errors; 18 | pub mod tests; 19 | pub mod types; 20 | pub mod utils; 21 | 22 | /// Create a Bot to interact with APIs. 23 | /// Returns a `Bot` configured with the provided bot token and chat id. 24 | /// 25 | /// # Arguments 26 | /// * `bot_token` - a string of bot token 27 | /// * `chat_id` - a chat id string to send message 28 | pub fn create_bot(bot_token: &str, chat_id: &str) -> Bot { 29 | Bot::new(bot_token, chat_id) 30 | } 31 | 32 | /// Send message asynchronously. 33 | /// Return `Result<(), ErrorResult>`. 34 | /// 35 | /// # Arguments 36 | /// 37 | /// * `bot` - `Bot` to send message to telegram's chat 38 | /// * `msg` - message to send 39 | /// * `options` - options for sending message 40 | pub async fn send_message( 41 | bot: &Bot, 42 | msg: &str, 43 | options: Option, 44 | ) -> Result<(), errors::ErrorResult> { 45 | bot.send_message(msg, options).await 46 | } 47 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use crate::{ 4 | types::{SendMessageOption, SendMessageParseMode}, 5 | *, 6 | }; // import lib.rs 7 | use std::{env, sync::Arc}; 8 | 9 | /// Reading bot token, and chat id for inteacting with telegram bot. 10 | /// 11 | /// Set environment variable as follows before running test 12 | /// `TEST_BOT_TOKEN` - telegram bot's token 13 | /// `TEST_CHAT_ID` - telegram bot's chat id 14 | /// 15 | /// These environment variable names are used for testing purpose only. 16 | fn get_keys() -> Result<(String, String), String> { 17 | dotenv::dotenv().ok(); 18 | 19 | let bot_token = env::var("TEST_BOT_TOKEN") 20 | .map_err(|_| "TEST_BOT_TOKEN environment variable is not set")?; 21 | let chat_id = 22 | env::var("TEST_CHAT_ID").map_err(|_| "TEST_CHAT_ID environment variable is not set")?; 23 | 24 | Ok((bot_token, chat_id)) 25 | } 26 | 27 | /// Create test bot instance with given bot token, and chat id. 28 | fn get_bot() -> Bot { 29 | let keys = get_keys().expect("Failed to read environment variables"); 30 | create_bot(&keys.0, &keys.1) 31 | } 32 | 33 | #[tokio::test] 34 | async fn test_send_message_simple() { 35 | let bot = get_bot(); 36 | 37 | let res = bot.send_message("test_send_message_simple", None).await; 38 | assert!(res.is_ok()); 39 | } 40 | 41 | #[tokio::test] 42 | async fn test_send_markdown_message() { 43 | let bot = get_bot(); 44 | let m1 = bot.send_message( 45 | r#"\[rustygram\] __MarkdownV2__ 46 | *async msg 1* 47 | `Tap to copy this text`\. 48 | You can visit my [website](https://github.com/extremelySunnyYK)\. 49 | Woot\!"#, 50 | Some(SendMessageOption { 51 | parse_mode: Some(SendMessageParseMode::MarkdownV2), 52 | }), 53 | ); 54 | 55 | let m2 = bot.send_message( 56 | r#"\[rustygram\] __MarkdownV2__ 57 | *async msg 2* 58 | `Tap to copy this text`\. 59 | You can visit my [website](https://github.com/extremelySunnyYK)\. 60 | Woot\!"#, 61 | Some(SendMessageOption { 62 | parse_mode: Some(SendMessageParseMode::MarkdownV2), 63 | }), 64 | ); 65 | 66 | assert!(m1.await.is_ok()); 67 | assert!(m2.await.is_ok()); 68 | } 69 | 70 | #[tokio::test] 71 | async fn test_send_html_message() { 72 | let bot = get_bot(); 73 | let m1 = bot.send_message( 74 | r#"[rustygram] HTML style - async msg 1 75 | Tap to copy this text. 76 | You can visit my website. 77 | Woot!"#, 78 | Some(SendMessageOption { 79 | parse_mode: Some(SendMessageParseMode::HTML), 80 | }), 81 | ); 82 | let m2 = bot.send_message( 83 | r#"[rustygram] HTML style - async msg 2 84 | Tap to copy this text. 85 | You can visit my website. 86 | Woot!"#, 87 | Some(SendMessageOption { 88 | parse_mode: Some(SendMessageParseMode::HTML), 89 | }), 90 | ); 91 | 92 | assert!(m1.await.is_ok()); 93 | assert!(m2.await.is_ok()); 94 | } 95 | #[tokio::test] 96 | async fn test_send_csv() { 97 | use std::fs::File; 98 | use std::io::Write; 99 | use tempfile::tempdir; 100 | 101 | // Create a temporary directory and file for testing 102 | let temp_dir = tempdir().unwrap(); 103 | let file_path = temp_dir.path().join("test.csv"); 104 | let mut file = File::create(&file_path).unwrap(); 105 | writeln!(file, "name,age\nJohn,30\nJane,25").unwrap(); 106 | 107 | let bot = get_bot(); 108 | let result = bot.send_csv(file_path.to_str().unwrap(), "test_csv").await; 109 | 110 | // If the result is an error, print the error message 111 | if let Err(e) = &result { 112 | println!("Error: {}", e); 113 | } 114 | 115 | // Assert that the result is Ok 116 | assert!(result.is_ok()); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | branches: [main] 6 | merge_group: 7 | 8 | name: Continuous integration 9 | 10 | env: 11 | RUSTDOCFLAGS: -Dwarnings 12 | RUST_BACKTRACE: short 13 | 14 | # ...existing environment variables... 15 | TEST_BOT_TOKEN: ${{ secrets.TEST_BOT_TOKEN }} 16 | TEST_CHAT_ID: ${{ secrets.TEST_CHAT_ID }} 17 | 18 | CARGO_INCREMENTAL: 0 19 | CARGO_NET_RETRY: 10 20 | RUSTUP_MAX_RETRIES: 10 21 | 22 | # When updating this, also update: 23 | # - crates/teloxide-core/src/codegen.rs 24 | # - rust-toolchain.toml 25 | rust_nightly: nightly-2023-09-27 26 | # When updating this, also update: 27 | # - **/README.md 28 | # - **/src/lib.rs 29 | # - down below in a matrix 30 | # - `Cargo.toml` 31 | # - **/CHANGELOG.md 32 | rust_msrv: 1.68.0 33 | 34 | CI: 1 35 | 36 | jobs: 37 | # Depends on all action that are required for a "successful" CI run. 38 | ci-pass: 39 | name: CI succeeded 40 | runs-on: ubuntu-latest 41 | if: always() 42 | 43 | needs: 44 | - fmt 45 | - test 46 | # - check-examples 47 | - clippy 48 | - doc 49 | 50 | steps: 51 | - name: Check whether the needed jobs succeeded or failed 52 | uses: re-actors/alls-green@release/v1 53 | with: 54 | jobs: ${{ toJSON(needs) }} 55 | 56 | fmt: 57 | name: fmt 58 | runs-on: ubuntu-latest 59 | 60 | steps: 61 | - name: Checkout 62 | uses: actions/checkout@v3 63 | 64 | - name: Install Rust ${{ env.rust_nightly }} 65 | uses: dtolnay/rust-toolchain@master 66 | with: 67 | toolchain: ${{ env.rust_nightly }} 68 | components: rustfmt 69 | 70 | - name: Cache Dependencies 71 | uses: Swatinem/rust-cache@v2 72 | 73 | - name: Check formatting 74 | run: | 75 | cargo fmt --all -- --check 76 | 77 | test: 78 | name: Test 79 | runs-on: ubuntu-latest 80 | strategy: 81 | matrix: 82 | rust: 83 | - stable 84 | - nightly 85 | 86 | include: 87 | - rust: stable 88 | toolchain: stable 89 | - rust: nightly 90 | toolchain: nightly-2023-09-27 91 | 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v3 95 | 96 | - name: Install Rust ${{ matrix.toolchain }} 97 | uses: dtolnay/rust-toolchain@master 98 | with: 99 | toolchain: ${{ matrix.toolchain }} 100 | 101 | - name: Cache Dependencies 102 | uses: Swatinem/rust-cache@v2 103 | 104 | # NB. Don't test (build) examples so we can use non-msrv features in them (--tests/--doc) 105 | - name: Compile 106 | run: | 107 | cargo +${{ matrix.toolchain }} test --tests --no-run --verbose 108 | 109 | - name: Test unit & integration tests 110 | env: 111 | TEST_BOT_TOKEN: ${{ secrets.TEST_BOT_TOKEN }} 112 | TEST_CHAT_ID: ${{ secrets.TEST_CHAT_ID }} 113 | run: | 114 | cargo +${{ matrix.toolchain }} test --tests --verbose 115 | 116 | - name: Test documentation tests 117 | if: ${{ matrix.rust != 'msrv' }} 118 | run: | 119 | cargo +${{ matrix.toolchain }} test --doc --verbose 120 | 121 | # check-examples: 122 | # runs-on: ubuntu-latest 123 | 124 | # steps: 125 | # - name: Checkout 126 | # uses: actions/checkout@v3 127 | 128 | # - name: Install Rust stable 129 | # uses: dtolnay/rust-toolchain@master 130 | # with: 131 | # toolchain: stable 132 | 133 | # - name: Cache Dependencies 134 | # uses: Swatinem/rust-cache@v2 135 | 136 | # - name: Check examples 137 | # run: | 138 | # cargo +stable check --examples 139 | 140 | # # TODO: prolly move it to a separate step? 141 | # - name: Check with no default features 142 | # run: | 143 | # cargo +stable check --no-default-features 144 | 145 | clippy: 146 | name: Run linter 147 | runs-on: ubuntu-latest 148 | 149 | steps: 150 | - name: Checkout 151 | uses: actions/checkout@v3 152 | 153 | - name: Install Rust ${{ env.rust_nightly }} 154 | uses: dtolnay/rust-toolchain@master 155 | with: 156 | toolchain: ${{ env.rust_nightly }} 157 | components: clippy 158 | 159 | - name: Cache Dependencies 160 | uses: Swatinem/rust-cache@v2 161 | 162 | - name: clippy 163 | run: | 164 | cargo clippy --all-targets 165 | 166 | doc: 167 | name: check docs 168 | runs-on: ubuntu-latest 169 | 170 | steps: 171 | - name: Checkout 172 | uses: actions/checkout@v3 173 | 174 | - name: Install Rust ${{ env.rust_nightly }} 175 | uses: dtolnay/rust-toolchain@master 176 | with: 177 | toolchain: ${{ env.rust_nightly }} 178 | 179 | - name: Cache Dependencies 180 | uses: Swatinem/rust-cache@v2 181 | 182 | - name: rustdoc 183 | run: | 184 | cargo doc 185 | -------------------------------------------------------------------------------- /example.rs: -------------------------------------------------------------------------------- 1 | // This is an example file of how rustygram can be integrated easily into your trading systems. 2 | // This example uses execution on Binance 3 | // In `.env` - `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` has to be declared first. 4 | 5 | use std::env; 6 | 7 | use chrono::DateTime; 8 | use chrono_tz::Tz; 9 | use dotenv::dotenv; 10 | use rustygram::bot::Bot; 11 | 12 | pub struct TradeSuccessNotification { 13 | pub listing_id: u32, 14 | pub ticker: String, 15 | pub market_buy_qty: f64, 16 | pub market_buy_price: f64, 17 | pub stop_loss_price: f64, 18 | pub take_profit_price: f64, 19 | pub timestamp: DateTime, 20 | } 21 | 22 | impl TradeSuccessNotification { 23 | fn craft_message(&self) -> String { 24 | format!( 25 | "listing ID: {}\nTicker: {}\nMarket Buy at {}qty at ${}\nStop loss: {}\nTake profit: {}\n{}", 26 | self.listing_id, 27 | self.ticker, 28 | self.market_buy_qty, 29 | self.market_buy_price, 30 | self.stop_loss_price, 31 | self.take_profit_price, 32 | self.timestamp 33 | ) 34 | } 35 | } 36 | 37 | pub struct TradeFailureNotification { 38 | pub listing_id: u32, 39 | pub ticker: String, 40 | pub timestamp: DateTime, 41 | pub error: String, 42 | } 43 | 44 | impl TradeFailureNotification { 45 | fn craft_message(&self) -> String { 46 | format!( 47 | "Listing ID: {}\nExecution failed for {} due to Error: {}\n{}", 48 | self.listing_id, self.ticker, self.error, self.timestamp 49 | ) 50 | } 51 | } 52 | 53 | pub async fn send_success_notification(bot: &Bot, message: &TradeSuccessNotification) { 54 | let message = message.craft_message(); 55 | let _ = bot.send_message(&message, None).await; 56 | } 57 | 58 | pub async fn send_failure_notification(bot: &Bot, message: &TradeFailureNotification) { 59 | let message = message.craft_message(); 60 | let _ = bot.send_message(&message, None).await; 61 | } 62 | 63 | pub async fn send_network_client_failure_notification(bot: &Bot, error: &str) { 64 | let message = format!("Network client failed with error: {}", error); 65 | let _ = bot.send_message(&message, None).await; 66 | } 67 | 68 | /// Create a new bot using the Telegram bot token and chat ID from the environment variables. 69 | /// 70 | /// # Example 71 | /// 72 | /// ```no_run 73 | /// use notifications::notifications::create_bot; 74 | /// 75 | /// # fn main() -> Result<(), Box> { 76 | /// // Assuming the environment variables are set 77 | /// let bot = create_bot(); 78 | /// # Ok(()) 79 | /// # } 80 | /// ``` 81 | pub fn create_bot() -> Bot { 82 | dotenv().ok(); 83 | let token = env::var("TELEGRAM_BOT_TOKEN") 84 | .unwrap_or_else(|_| "".to_string()); 85 | let chat_id = env::var("TELEGRAM_CHAT_ID").unwrap_or_else(|_| "".to_string()); 86 | Bot::new(token, chat_id) 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use super::*; 92 | 93 | #[test] 94 | fn test_trade_success_notification_craft_message() { 95 | let time = chrono::DateTime::parse_from_rfc3339("2020-12-31T23:59:59.999999999Z").unwrap(); 96 | let singapore_time = time.with_timezone(&chrono_tz::Singapore); 97 | let notification = TradeSuccessNotification { 98 | listing_id: 1, 99 | ticker: "BTC".to_string(), 100 | market_buy_qty: 0.1, 101 | market_buy_price: 10000.0, 102 | stop_loss_price: 9000.0, 103 | take_profit_price: 11000.0, 104 | timestamp: singapore_time, 105 | }; 106 | 107 | let message = notification.craft_message(); 108 | assert_eq!( 109 | message, 110 | "listing ID: 1\nTicker: BTC\nMarket Buy at 0.1qty at $10000\nStop loss: 9000\nTake profit: 11000\n2021-01-01 07:59:59.999999999 +08" 111 | ); 112 | } 113 | 114 | #[test] 115 | fn test_trade_failure_notification_craft_message() { 116 | let time = chrono::DateTime::parse_from_rfc3339("2020-12-31T23:59:59.999999999Z").unwrap(); 117 | let singapore_time = time.with_timezone(&chrono_tz::Singapore); 118 | 119 | let notification = TradeFailureNotification { 120 | listing_id: 1, 121 | ticker: "BTC".to_string(), 122 | timestamp: singapore_time, 123 | error: "Insufficient funds".to_string(), 124 | }; 125 | 126 | let message = notification.craft_message(); 127 | assert_eq!( 128 | message, 129 | "listing ID: 1\nExecution failed for BTC due to Error: Insufficient funds\n2021-01-01 07:59:59.999999999 +08" 130 | ); 131 | } 132 | 133 | #[tokio::test] 134 | #[ignore] 135 | async fn test_send_success_notification() { 136 | let bot = create_bot(); 137 | let time = chrono::DateTime::parse_from_rfc3339("2020-12-31T23:59:59.999999999Z").unwrap(); 138 | let singapore_time = time.with_timezone(&chrono_tz::Singapore); 139 | let notification = TradeSuccessNotification { 140 | listing_id: 1, 141 | ticker: "BTC".to_string(), 142 | market_buy_qty: 0.1, 143 | market_buy_price: 10000.0, 144 | stop_loss_price: 9000.0, 145 | take_profit_price: 11000.0, 146 | timestamp: singapore_time, 147 | }; 148 | 149 | send_success_notification(&bot, ¬ification).await; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

⚡rustygram

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ⚡rustygram is a minimal and blazing fast telegram notification framework using [Rust](https://www.rust-lang.org/). Abstracts away the Telegram API complexity so your app doesn't have to worry about the underlying implementation. 18 | 19 |
20 | 21 | ## Highlights 22 | 23 | - Easily integrate rustygram into your rust application to quickly send messages to Telegram bots, groups, and channels. 24 | 25 | - Send asynchronous notifications in a reliable way. 26 | 27 | ## API overview 28 | 29 | - `create_bot` - create a bot instance consistsing of Telegram's bot token, and target chat_id 30 | - `send_message` - call Telegram bot's API sendMessage to send message asynchronously 31 | 32 | ## Examples 33 | 34 | ### Send in simple way 35 | 36 | ```rust 37 | fn main() { 38 | let instance = rustygram::create_bot("123456:123456", "-1000000"); 39 | if let Err(_) = rustygram::send_message(&instance, "Hello world", None) { 40 | // error handling here... 41 | } 42 | } 43 | ``` 44 | 45 | ### Send in `MarkdownV2` or `HTML` style message 46 | 47 | Send message in `MarkdownV2` 48 | 49 | ```rust 50 | use rustygram::types::{SendMessageOption, SendMessageParseMode}; 51 | 52 | fn main() { 53 | let instance = rustygram::create_bot("16", "-1s00"); 54 | let option = SendMessageOption { parse_mode: Some(SendMessageParseMode::MarkdownV2) }; 55 | 56 | // note on two spaces at the end of the line for a new line in markdown 57 | if let Err(_) = rustygram::send_message(&instance, 58 | r#"__Hello world__ 59 | `Tap to copy this text` 60 | Visit my [website](https://yong-kang.super.site/)"#, Some(option)) { 61 | // error handling here... 62 | } 63 | } 64 | ``` 65 | 66 | Send messsage in `HTML` 67 | 68 | ```rust 69 | use rustygram::types::{SendMessageOption, SendMessageParseMode}; 70 | 71 | fn main() { 72 | let instance = rustygram::create_instance("189:blablabla", "-10"); 73 | let option = SendMessageOption { parse_mode: Some(SendMessageParseMode::HTML) }; 74 | 75 | if let Err(_) = rustygram::send_message(&instance, 76 | r#"Hello world 77 | Tap to copy this text 78 | Visit my website"#, Some(option)) { 79 | // error handling here... 80 | } 81 | } 82 | ``` 83 | 84 | ### Setting up and testing it as a class 85 | 86 | - Check out [example.rs](https://github.com/yongkangc/rustygram/blob/main/example.rs) where there is a concrete example with tests 87 | 88 | ### Sending CSV file 89 | 90 | ```rust 91 | use rustygram::types::{SendMessageOption, SendMessageParseMode}; 92 | 93 | fn main() { 94 | let instance = rustygram::create_bot("189:blablabla", "-10"); 95 | let option = SendMessageOption { parse_mode: Some(SendMessageParseMode::MarkdownV2) }; 96 | 97 | if let Err(_) = rustygram::send_csv(&instance, "example.csv", Some(option)) { 98 | // error handling here... 99 | } 100 | } 101 | ``` 102 | 103 | - Refer to tests.rs for more reference on how to send a CSV file 104 | 105 | ## Setting up your environment 106 | 107 | 1. [Download Rust](http://rustup.rs/). 108 | 2. Create a new bot using [@Botfather](https://t.me/botfather) to get a token in the format `189:blablabla`. 109 | 3. Initialise the `BOT_TOKEN` environmental variable to your token: 110 | 111 | ```bash 112 | # Unix-like 113 | $ export BOT_TOKEN= 114 | 115 | # Windows command line 116 | $ set BOT_TOKEN= 117 | 118 | # Windows PowerShell 119 | $ $env:BOT_TOKEN= 120 | ``` 121 | 122 | 4. Make sure that your Rust compiler is up to date (`rustygram` currently requires rustc at least version 1.68): 123 | 124 | ```bash 125 | # If you're using stable 126 | $ rustup update stable 127 | $ rustup override set stable 128 | 129 | # If you're using nightly 130 | $ rustup update nightly 131 | $ rustup override set nightly 132 | ``` 133 | 134 | 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: 135 | 136 | ```toml 137 | [dependencies] 138 | rustygram = "0.1" 139 | log = "0.4" 140 | pretty_env_logger = "0.4" 141 | tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } 142 | ``` 143 | 144 | ## Tests 145 | 146 | You can test by define the following two environment variables 147 | 148 | - `TEST_BOT_TOKEN` - telegram bot's token 149 | - `TEST_CHAT_ID` - telegram bot's chat id 150 | 151 | then execute 152 | 153 | `cargo test` 154 | 155 | some tests will send a single, or multiple messages to a specified chat id on behalf 156 | of such telegram bot. Please take a look at `src/tests.rs`. 157 | 158 | ### Note 159 | 160 | You can utilize this telegram bot `@username_to_id_bot` to get your 161 | telegram channel's `chat_id`. 162 | 163 | ## Contributing 164 | 165 | See [`CONRIBUTING.md`](CONTRIBUTING.md). 166 | 167 | ## Acknowledgements 168 | 169 | This project is heavily inspired by [teloxide](https://github.com/ExtremelySunnyYk/rustygram). However it is not a fork of teloxide, but a complete rewrite in order to make it more minimal and faster for the use case of sending notifications on telegram. 170 | 171 | The simplistic design is inspired by [rustelebot](https://github.com/haxpor/rustelebot) but with more modular and extensible design, also changing the underlying libraries used to be more modern. 172 | 173 | ## License 174 | 175 | MIT, [Chia Yong Kang](https://www.linkedin.com/in/chiayong-eth/) 176 | -------------------------------------------------------------------------------- /src/bot.rs: -------------------------------------------------------------------------------- 1 | use reqwest::header::HeaderMap; 2 | use reqwest::header::HeaderValue; 3 | use reqwest::multipart; 4 | use reqwest::Client; 5 | use std::fs; 6 | use std::io::Read; 7 | use std::{sync::Arc, time::Duration}; 8 | 9 | use crate::{ 10 | errors::{ErrorResult, TelegramErrorResult}, 11 | types::{RequestObj, SendMessageOption, StatusCode}, 12 | utils, 13 | }; 14 | 15 | pub const TELEGRAM_API_URL: &str = "https://api.telegram.org"; 16 | const SEND_MESSAGE_METHOD: &str = "sendMessage"; 17 | const SEND_MEDIA_METHOD: &str = "sendMediaGroup"; 18 | 19 | /// A requests sender. 20 | /// 21 | /// This is the main type of the library, it allows to send requests to the 22 | /// [Telegram Bot API] and download files. 23 | /// 24 | /// ## Clone cost 25 | /// 26 | /// `Bot::clone` is relatively cheap, so if you need to share `Bot`, it's 27 | /// recommended to clone it, instead of wrapping it in [`Arc<_>`]. 28 | /// 29 | /// [`Arc`]: std::sync::Arc 30 | /// [Telegram Bot API]: https://core.telegram.org/bots/api 31 | #[must_use] 32 | #[derive(Debug, Clone)] 33 | pub struct Bot { 34 | pub token: Arc, 35 | pub chat_id: Arc, 36 | pub api_url: Arc, 37 | pub client: Client, 38 | } 39 | 40 | /// Constructors 41 | impl Bot { 42 | /// Creates a new `Bot` with the specified token and the default 43 | /// [http-client](reqwest::Client). 44 | /// 45 | /// # Panics 46 | /// If it cannot create [`reqwest::Client`]. 47 | /// 48 | pub fn new(token: S, chat_id: S) -> Self 49 | where 50 | S: Into, 51 | { 52 | let client = default_reqwest_settings() 53 | .build() 54 | .expect("Client creation failed"); 55 | 56 | Self::with_client(token, chat_id, client) 57 | } 58 | 59 | /// Creates a new `Bot` with the specified token and your 60 | /// [`reqwest::Client`]. 61 | /// 62 | /// # Caution 63 | /// 64 | /// Your custom client might not be configured correctly to be able to work 65 | /// in long time durations, see [issue 223]. 66 | /// 67 | /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html 68 | /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 69 | pub fn with_client(token: S, chat_id: S, client: Client) -> Self 70 | where 71 | S: Into, 72 | { 73 | let token = Into::::into(token).into(); 74 | let chat_id = Into::::into(chat_id).into(); 75 | let api_url = Arc::new( 76 | reqwest::Url::parse(TELEGRAM_API_URL) 77 | .expect("Failed to parse default Telegram bot API url"), 78 | ); 79 | 80 | Self { 81 | token, 82 | chat_id, 83 | api_url, 84 | client, 85 | } 86 | } 87 | } 88 | 89 | /// Core Functionality 90 | impl Bot { 91 | /// Sends a request to the Telegram Bot API asynchronously 92 | pub async fn send_message( 93 | &self, 94 | msg: &str, 95 | options: Option, 96 | ) -> Result<(), ErrorResult> { 97 | let request_json_obj = self.build_request_obj(msg, options); 98 | 99 | let response = self 100 | .client 101 | .post(method_url( 102 | self.api_url(), 103 | self.token(), 104 | SEND_MESSAGE_METHOD, 105 | )) 106 | .json(&request_json_obj) 107 | .send() 108 | .await; 109 | 110 | match response { 111 | Ok(res) => { 112 | if res.status().is_success() { 113 | Ok(()) 114 | } else { 115 | match res.json::().await { 116 | Ok(err_result) => { 117 | return utils::create_error_result_str( 118 | StatusCode::ErrorInternalError, 119 | &err_result.description.to_owned(), 120 | ) 121 | } 122 | Err(_) => { 123 | return utils::create_error_result_str( 124 | StatusCode::ErrorInternalError, 125 | "Error converting telegram error response to json", 126 | ) 127 | } 128 | } 129 | } 130 | } 131 | Err(e) => { 132 | return utils::create_error_result_str( 133 | StatusCode::ErrorInternalError, 134 | &format!("Error sending HTTP request; err={}", e), 135 | ) 136 | } 137 | } 138 | } 139 | 140 | pub async fn send_csv(&self, filepath: &str, caption: &str) -> Result<(), ErrorResult> { 141 | let mut file = fs::File::open(filepath)?; 142 | let mut contents = Vec::new(); 143 | file.read_to_end(&mut contents)?; 144 | 145 | let file_name = std::path::Path::new(filepath) 146 | .file_name() 147 | .and_then(|n| n.to_str()) 148 | .unwrap_or("file.csv"); 149 | 150 | let part = multipart::Part::bytes(contents) 151 | .file_name(file_name.to_string()) 152 | .mime_str("text/csv")?; 153 | 154 | let media = serde_json::json!([ 155 | { 156 | "type": "document", 157 | "media": "attach://file", 158 | "caption": caption 159 | } 160 | ]); 161 | 162 | let form = multipart::Form::new() 163 | .text("chat_id", self.chat_id.to_string()) 164 | .text("media", media.to_string()) 165 | .part("file", part); 166 | 167 | let response = self 168 | .client 169 | .post(method_url(self.api_url(), self.token(), "sendMediaGroup")) 170 | .header(reqwest::header::CONTENT_TYPE, "multipart/form-data") 171 | .multipart(form) 172 | .send() 173 | .await?; 174 | 175 | if response.status().is_success() { 176 | Ok(()) 177 | } else { 178 | let err_result = 179 | response 180 | .json::() 181 | .await 182 | .map_err(|_| ErrorResult { 183 | code: StatusCode::ErrorInternalError.as_u16(), 184 | msg: "Error converting telegram error response to json".to_string(), 185 | })?; 186 | 187 | Err(ErrorResult { 188 | code: StatusCode::ErrorInternalError.as_u16(), 189 | msg: err_result.description.to_owned(), 190 | }) 191 | } 192 | } 193 | 194 | fn build_request_obj(&self, msg: &str, options: Option) -> RequestObj { 195 | let parse_mode = options 196 | .as_ref() 197 | .and_then(|option| option.parse_mode.as_ref()) // Avoid repeated unwrap calls 198 | .map(|mode| utils::get_send_message_parse_mode_str(mode).to_owned()); 199 | 200 | RequestObj::new(&self.chat_id, msg, parse_mode) 201 | } 202 | } 203 | 204 | /// Getters 205 | impl Bot { 206 | /// Returns currently used token. 207 | #[must_use] 208 | pub fn token(&self) -> &str { 209 | &self.token 210 | } 211 | 212 | /// Returns currently used http-client. 213 | #[must_use] 214 | pub fn client(&self) -> &Client { 215 | &self.client 216 | } 217 | 218 | /// Returns currently used token API url. 219 | #[must_use] 220 | pub fn api_url(&self) -> reqwest::Url { 221 | reqwest::Url::clone(&*self.api_url) 222 | } 223 | } 224 | 225 | /// ********************** Utilities ********************** 226 | 227 | /// Returns a reqwest client builder with default settings. 228 | /// 229 | /// Client built from default settings is supposed to work over long time 230 | /// durations, see the [issue 223]. 231 | /// 232 | /// The current settings are: 233 | /// - A connection timeout of 5 seconds. 234 | /// - A timeout of 17 seconds. 235 | /// - `tcp_nodelay` is on. 236 | /// 237 | /// ## Notes 238 | /// 239 | /// 1. The settings may change in the future. 240 | /// 2. If you are using the polling mechanism to get updates, the timeout 241 | /// configured in the client should be bigger than the polling timeout. 242 | /// 3. If you alter the current settings listed above, your bot will not be 243 | /// guaranteed to work over long time durations. 244 | /// 245 | /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 246 | fn default_reqwest_settings() -> reqwest::ClientBuilder { 247 | reqwest::Client::builder() 248 | .connect_timeout(Duration::from_secs(5)) 249 | .timeout(Duration::from_secs(17)) 250 | .tcp_nodelay(true) 251 | } 252 | 253 | /// Creates URL for making HTTPS requests. See the [Telegram documentation]. 254 | /// 255 | /// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests 256 | fn method_url(base: reqwest::Url, token: &str, method_name: &str) -> reqwest::Url { 257 | base.join(&format!("/bot{token}/{method_name}")) 258 | .expect("failed to format url") 259 | } 260 | 261 | #[cfg(test)] 262 | mod tests { 263 | use crate::bot::{method_url, SEND_MESSAGE_METHOD, TELEGRAM_API_URL}; 264 | 265 | #[test] 266 | fn method_url_test() { 267 | let url = method_url( 268 | reqwest::Url::parse(TELEGRAM_API_URL).unwrap(), 269 | "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", 270 | SEND_MESSAGE_METHOD, 271 | ); 272 | 273 | assert_eq!( 274 | url.as_str(), 275 | format!( 276 | "https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/{}", 277 | SEND_MESSAGE_METHOD 278 | ) 279 | ); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /CODE_STYLE.md: -------------------------------------------------------------------------------- 1 | # Code style 2 | 3 | This is a description of a coding style that every contributor must follow. 4 | Please, read the whole document before you start pushing code. 5 | 6 | ## Generics 7 | 8 | All trait bounds should be written in `where`: 9 | 10 | ```rust 11 | // GOOD 12 | pub fn new(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self 13 | where 14 | N: Into, 15 | T: Into, 16 | P: Into, 17 | E: Into, 18 | { ... } 19 | 20 | // BAD 21 | pub fn new, 22 | T: Into, 23 | P: Into, 24 | E: Into> 25 | (user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self { ... } 26 | ``` 27 | 28 | ```rust 29 | // GOOD 30 | impl Trait for Wrap 31 | where 32 | T: Trait 33 | { ... } 34 | 35 | // BAD 36 | impl Trait for Wrap { ... } 37 | ``` 38 | 39 | **Rationale:** 40 | 41 | - `where` clauses are easier to read when there are a lot of bounds 42 | - uniformity 43 | 44 | ## Documentation comments 45 | 46 | 1. Documentation must describe _what_ your code does and mustn't describe _how_ your code does it and bla-bla-bla. 47 | 2. Be sure that your comments follow the grammar, including punctuation, the first capital letter and so on: 48 | 49 | ```rust 50 | // GOOD 51 | /// This function makes a request to Telegram. 52 | pub fn make_request(url: &str) -> String { ... } 53 | 54 | // BAD 55 | /// this function make request to telegram 56 | pub fn make_request(url: &str) -> String { ... } 57 | ``` 58 | 59 | 3. Do not use ending punctuation in short list items (usually containing just one phrase or sentence): 60 | 61 | ```md 62 | 63 | 64 | - Handle different kinds of Update 65 | - Pass dependencies to handlers 66 | - Disable a default Ctrl-C handling 67 | 68 | 69 | 70 | - Handle different kinds of Update. 71 | - Pass dependencies to handlers. 72 | - Disable a default Ctrl-C handling. 73 | 74 | 75 | 76 | - Handle different kinds of Update; 77 | - Pass dependencies to handlers; 78 | - Disable a default Ctrl-C handling; 79 | ``` 80 | 81 | 4. Link resources in your comments when possible: 82 | ```rust 83 | /// Download a file from Telegram. 84 | /// 85 | /// `path` can be obtained from the [`Bot::get_file`]. 86 | /// 87 | /// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see 88 | /// [`Bot::download_file`]. 89 | /// 90 | /// [`Bot::get_file`]: crate::bot::Bot::get_file 91 | /// [`AsyncWrite`]: tokio::io::AsyncWrite 92 | /// [`tokio::fs::File`]: tokio::fs::File 93 | /// [`Bot::download_file`]: crate::Bot::download_file 94 | ``` 95 | 5. Write `rustygram`, `rustygram-macros`, and `rustygram-core`, not "rustygram", "rustygram", "rustygram-macros" or any other variant. 96 | 97 | ## Use `Self` where possible 98 | 99 | When referring to the type for which block is implemented, prefer using `Self`, rather than the name of the type: 100 | 101 | ```rust 102 | impl ErrorKind { 103 | // GOOD 104 | fn print(&self) { 105 | Self::Io => println!("Io"), 106 | Self::Network => println!("Network"), 107 | Self::Json => println!("Json"), 108 | } 109 | 110 | // BAD 111 | fn print(&self) { 112 | ErrorKind::Io => println!("Io"), 113 | ErrorKind::Network => println!("Network"), 114 | ErrorKind::Json => println!("Json"), 115 | } 116 | } 117 | ``` 118 | 119 | ```rust 120 | impl<'a> AnswerCallbackQuery<'a> { 121 | // GOOD 122 | fn new(bot: &'a Bot, callback_query_id: C) -> Self 123 | where 124 | C: Into, 125 | { ... } 126 | 127 | // BAD 128 | fn new(bot: &'a Bot, callback_query_id: C) -> AnswerCallbackQuery<'a> 129 | where 130 | C: Into, 131 | { ... } 132 | } 133 | ``` 134 | 135 | **Rationale:** `Self` is generally shorter and it's easier to copy-paste code or rename the type. 136 | 137 | ## Avoid duplication in fields names 138 | 139 | ```rust 140 | struct Message { 141 | // GOOD 142 | #[serde(rename = "message_id")] 143 | id: MessageId, 144 | 145 | // BAD 146 | message_id: MessageId, 147 | } 148 | ``` 149 | 150 | **Rationale:** duplication blurs the focus of code, making it unnecessarily longer. 151 | 152 | ## Conventional generic names 153 | 154 | Use a generic parameter name `S` for streams, `Fut` for futures, `F` for functions (where possible). 155 | 156 | **Rationale:** uniformity. 157 | 158 | ## Deriving traits 159 | 160 | Derive `Copy`, `Clone`, `Eq`, `PartialEq`, `Hash` and `Debug` for public types when possible. 161 | 162 | **Rationale:** these traits can be useful for users and can be implemented for most types. 163 | 164 | Derive `Default` when there is a reasonable default value for the type. 165 | 166 | **Rationale:** `Default` plays nicely with generic code (for example, `mem::take`). 167 | 168 | ## `Into`-polymorphism 169 | 170 | Use `T: Into` when this can simplify user code. 171 | I.e. when there are types that implement `Into` that are likely to be passed to this function. 172 | 173 | **Rationale:** conversions unnecessarily complicate caller code and can be confusing for beginners. 174 | 175 | ## `must_use` 176 | 177 | Always mark functions as `#[must_use]` if they don't have side effects and the only reason to call them is to get the result: 178 | 179 | ```rust 180 | impl User { 181 | // GOOD 182 | #[must_use] 183 | fn full_name(&self) -> String { 184 | format!("{} {}", user.first_name, user.last_name) 185 | } 186 | } 187 | ``` 188 | 189 | **Rationale:** users will get warnings if they forgot to do something with the result, potentially preventing bugs. 190 | 191 | ## Creating boxed futures 192 | 193 | Prefer `Box::pin(async { ... })` instead of `async { ... }.boxed()`. 194 | 195 | **Rationale:** the former is generally formatted better by rustfmt. 196 | 197 | ## Full paths for logging 198 | 199 | Always write `log::!(...)` instead of importing `use log::;` and invoking `!(...)`. 200 | 201 | ```rust 202 | // GOOD 203 | log::warn!("Everything is on fire"); 204 | 205 | // BAD 206 | use log::warn; 207 | 208 | warn!("Everything is on fire"); 209 | ``` 210 | 211 | **Rationale:** 212 | 213 | - Less polluted import blocks 214 | - Uniformity 215 | 216 | ## `&str` -> `String` conversion 217 | 218 | Prefer using `.to_owned()`, rather than `.to_string()`, `.into()`, `String::from`, etc. 219 | 220 | **Rationale:** uniformity, intent clarity. 221 | 222 | ## Order of imports 223 | 224 | Separate import groups with blank lines. Use one use per crate. 225 | 226 | Module declarations come before the imports. 227 | Order them in "suggested reading order" for a person new to the code base. 228 | 229 | ```rust 230 | mod x; 231 | mod y; 232 | 233 | // First std. 234 | use std::{ ... } 235 | 236 | // Second, external crates (both crates.io crates and other rust-analyzer crates). 237 | use crate_foo::{ ... } 238 | use crate_bar::{ ... } 239 | 240 | // Then current crate. 241 | use crate::{} 242 | 243 | // Finally, parent and child modules, but prefer `use crate::`. 244 | use super::{} 245 | 246 | // Re-exports are treated as item definitions rather than imports, so they go 247 | // after imports and modules. Use them sparingly. 248 | pub use crate::x::Z; 249 | ``` 250 | 251 | **Rationale:** 252 | 253 | - Reading order is important for new contributors 254 | - Grouping by crate allows spotting unwanted dependencies easier 255 | - Consistency 256 | 257 | ## Import Style 258 | 259 | When implementing traits from `std::fmt` import the module: 260 | 261 | ```rust 262 | // GOOD 263 | use std::fmt; 264 | 265 | impl fmt::Display for RenameError { 266 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .. } 267 | } 268 | 269 | // BAD 270 | impl std::fmt::Display for RenameError { 271 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. } 272 | } 273 | ``` 274 | 275 | **Rationale:** 276 | 277 | - Makes it clear that a trait is implemented, rather than used 278 | - Less typing 279 | 280 | Prefer `use crate::foo::bar` to `use super::bar` or `use self::bar::baz`. **Rationale:** 281 | 282 | - Works in all cases 283 | - Consistency 284 | 285 | ## Order of Items 286 | 287 | Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on. People read things from top to bottom, so place most important things first. 288 | 289 | Specifically, if all items except one are private, always put the non-private item on top: 290 | 291 | ```rust 292 | // GOOD 293 | pub(crate) fn frobnicate() { 294 | Helper::act() 295 | } 296 | 297 | #[derive(Default)] 298 | struct Helper { stuff: i32 } 299 | 300 | impl Helper { 301 | fn act(&self) { 302 | 303 | } 304 | } 305 | 306 | // BAD 307 | #[derive(Default)] 308 | struct Helper { stuff: i32 } 309 | 310 | pub(crate) fn frobnicate() { 311 | Helper::act() 312 | } 313 | 314 | impl Helper { 315 | fn act(&self) { 316 | 317 | } 318 | } 319 | ``` 320 | 321 | If there's a mixture of private and public items, put public items first. 322 | 323 | Put structs and enums first, functions and impls last. Order type declarations in a top-down manner: 324 | 325 | ```rust 326 | // GOOD 327 | struct Parent { 328 | children: Vec 329 | } 330 | 331 | struct Child; 332 | 333 | impl Parent { 334 | } 335 | 336 | impl Child { 337 | } 338 | 339 | // BAD 340 | struct Child; 341 | 342 | impl Child { 343 | } 344 | 345 | struct Parent { 346 | children: Vec 347 | } 348 | 349 | impl Parent { 350 | } 351 | ``` 352 | 353 | **Rationale:** 354 | 355 | - Easier to get a sense of the API by visually scanning the file 356 | - If function bodies are folded in the editor, the source code should be read as documentation for the public API 357 | 358 | ## Early Returns 359 | 360 | Do use early returns: 361 | 362 | ```rust 363 | // GOOD 364 | fn foo() -> Option { 365 | if !condition() { 366 | return None; 367 | } 368 | 369 | Some(...) 370 | } 371 | 372 | // BAD 373 | fn foo() -> Option { 374 | if condition() { 375 | Some(...) 376 | } else { 377 | None 378 | } 379 | } 380 | ``` 381 | 382 | **Rationale:** reduce cognitive stack usage. 383 | 384 | ## If-let 385 | 386 | Avoid the `if let ... { } else { }` construct, use `match` instead: 387 | 388 | ```rust 389 | // GOOD 390 | match ctx.expected_type.as_ref() { 391 | Some(expected_type) => completion_ty == expected_type && !expected_type.is_unit(), 392 | None => false, 393 | } 394 | 395 | // BAD 396 | if let Some(expected_type) = ctx.expected_type.as_ref() { 397 | completion_ty == expected_type && !expected_type.is_unit() 398 | } else { 399 | false 400 | } 401 | ``` 402 | 403 | **Rationale:** 404 | 405 | - `match` is almost always more compact 406 | - The `else` branch can get a more precise pattern: `None` or `Err(_)` instead of `_` 407 | 408 | ## Empty Match Arms 409 | 410 | Use `=> (),` when a match arm is intentionally empty: 411 | 412 | ```rust 413 | // GOOD 414 | match result { 415 | Ok(_) => (), 416 | Err(err) => error!("{}", err), 417 | } 418 | 419 | // BAD 420 | match result { 421 | Ok(_) => {} 422 | Err(err) => error!("{}", err), 423 | } 424 | ``` 425 | 426 | **Rationale:** consistency. 427 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.1.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 25 | 26 | [[package]] 27 | name = "backtrace" 28 | version = "0.3.69" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 31 | dependencies = [ 32 | "addr2line", 33 | "cc", 34 | "cfg-if", 35 | "libc", 36 | "miniz_oxide", 37 | "object", 38 | "rustc-demangle", 39 | ] 40 | 41 | [[package]] 42 | name = "base64" 43 | version = "0.21.5" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "1.3.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "2.4.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 58 | 59 | [[package]] 60 | name = "bumpalo" 61 | version = "3.14.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 64 | 65 | [[package]] 66 | name = "bytes" 67 | version = "1.5.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 70 | 71 | [[package]] 72 | name = "cc" 73 | version = "1.0.83" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 76 | dependencies = [ 77 | "libc", 78 | ] 79 | 80 | [[package]] 81 | name = "cfg-if" 82 | version = "1.0.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 85 | 86 | [[package]] 87 | name = "core-foundation" 88 | version = "0.9.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 91 | dependencies = [ 92 | "core-foundation-sys", 93 | "libc", 94 | ] 95 | 96 | [[package]] 97 | name = "core-foundation-sys" 98 | version = "0.8.4" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 101 | 102 | [[package]] 103 | name = "dotenv" 104 | version = "0.15.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 107 | 108 | [[package]] 109 | name = "encoding_rs" 110 | version = "0.8.33" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 113 | dependencies = [ 114 | "cfg-if", 115 | ] 116 | 117 | [[package]] 118 | name = "equivalent" 119 | version = "1.0.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 122 | 123 | [[package]] 124 | name = "errno" 125 | version = "0.3.8" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 128 | dependencies = [ 129 | "libc", 130 | "windows-sys 0.52.0", 131 | ] 132 | 133 | [[package]] 134 | name = "fastrand" 135 | version = "2.0.1" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 138 | 139 | [[package]] 140 | name = "fnv" 141 | version = "1.0.7" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 144 | 145 | [[package]] 146 | name = "foreign-types" 147 | version = "0.3.2" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 150 | dependencies = [ 151 | "foreign-types-shared", 152 | ] 153 | 154 | [[package]] 155 | name = "foreign-types-shared" 156 | version = "0.1.1" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 159 | 160 | [[package]] 161 | name = "form_urlencoded" 162 | version = "1.2.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 165 | dependencies = [ 166 | "percent-encoding", 167 | ] 168 | 169 | [[package]] 170 | name = "futures-channel" 171 | version = "0.3.29" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" 174 | dependencies = [ 175 | "futures-core", 176 | ] 177 | 178 | [[package]] 179 | name = "futures-core" 180 | version = "0.3.29" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" 183 | 184 | [[package]] 185 | name = "futures-sink" 186 | version = "0.3.29" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" 189 | 190 | [[package]] 191 | name = "futures-task" 192 | version = "0.3.29" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" 195 | 196 | [[package]] 197 | name = "futures-util" 198 | version = "0.3.29" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" 201 | dependencies = [ 202 | "futures-core", 203 | "futures-task", 204 | "pin-project-lite", 205 | "pin-utils", 206 | ] 207 | 208 | [[package]] 209 | name = "gimli" 210 | version = "0.28.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 213 | 214 | [[package]] 215 | name = "h2" 216 | version = "0.3.22" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" 219 | dependencies = [ 220 | "bytes", 221 | "fnv", 222 | "futures-core", 223 | "futures-sink", 224 | "futures-util", 225 | "http", 226 | "indexmap", 227 | "slab", 228 | "tokio", 229 | "tokio-util", 230 | "tracing", 231 | ] 232 | 233 | [[package]] 234 | name = "hashbrown" 235 | version = "0.14.2" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" 238 | 239 | [[package]] 240 | name = "http" 241 | version = "0.2.11" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 244 | dependencies = [ 245 | "bytes", 246 | "fnv", 247 | "itoa", 248 | ] 249 | 250 | [[package]] 251 | name = "http-body" 252 | version = "0.4.5" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 255 | dependencies = [ 256 | "bytes", 257 | "http", 258 | "pin-project-lite", 259 | ] 260 | 261 | [[package]] 262 | name = "httparse" 263 | version = "1.8.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 266 | 267 | [[package]] 268 | name = "httpdate" 269 | version = "1.0.3" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 272 | 273 | [[package]] 274 | name = "hyper" 275 | version = "0.14.27" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 278 | dependencies = [ 279 | "bytes", 280 | "futures-channel", 281 | "futures-core", 282 | "futures-util", 283 | "h2", 284 | "http", 285 | "http-body", 286 | "httparse", 287 | "httpdate", 288 | "itoa", 289 | "pin-project-lite", 290 | "socket2 0.4.10", 291 | "tokio", 292 | "tower-service", 293 | "tracing", 294 | "want", 295 | ] 296 | 297 | [[package]] 298 | name = "hyper-tls" 299 | version = "0.5.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 302 | dependencies = [ 303 | "bytes", 304 | "hyper", 305 | "native-tls", 306 | "tokio", 307 | "tokio-native-tls", 308 | ] 309 | 310 | [[package]] 311 | name = "idna" 312 | version = "0.4.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 315 | dependencies = [ 316 | "unicode-bidi", 317 | "unicode-normalization", 318 | ] 319 | 320 | [[package]] 321 | name = "indexmap" 322 | version = "2.1.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 325 | dependencies = [ 326 | "equivalent", 327 | "hashbrown", 328 | ] 329 | 330 | [[package]] 331 | name = "ipnet" 332 | version = "2.9.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 335 | 336 | [[package]] 337 | name = "itoa" 338 | version = "1.0.9" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 341 | 342 | [[package]] 343 | name = "js-sys" 344 | version = "0.3.65" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" 347 | dependencies = [ 348 | "wasm-bindgen", 349 | ] 350 | 351 | [[package]] 352 | name = "lazy_static" 353 | version = "1.4.0" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 356 | 357 | [[package]] 358 | name = "libc" 359 | version = "0.2.153" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 362 | 363 | [[package]] 364 | name = "linux-raw-sys" 365 | version = "0.4.13" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 368 | 369 | [[package]] 370 | name = "log" 371 | version = "0.4.20" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 374 | 375 | [[package]] 376 | name = "memchr" 377 | version = "2.6.4" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 380 | 381 | [[package]] 382 | name = "mime" 383 | version = "0.3.17" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 386 | 387 | [[package]] 388 | name = "mime_guess" 389 | version = "2.0.4" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 392 | dependencies = [ 393 | "mime", 394 | "unicase", 395 | ] 396 | 397 | [[package]] 398 | name = "miniz_oxide" 399 | version = "0.7.1" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 402 | dependencies = [ 403 | "adler", 404 | ] 405 | 406 | [[package]] 407 | name = "mio" 408 | version = "0.8.9" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" 411 | dependencies = [ 412 | "libc", 413 | "wasi", 414 | "windows-sys 0.48.0", 415 | ] 416 | 417 | [[package]] 418 | name = "native-tls" 419 | version = "0.2.11" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 422 | dependencies = [ 423 | "lazy_static", 424 | "libc", 425 | "log", 426 | "openssl", 427 | "openssl-probe", 428 | "openssl-sys", 429 | "schannel", 430 | "security-framework", 431 | "security-framework-sys", 432 | "tempfile", 433 | ] 434 | 435 | [[package]] 436 | name = "object" 437 | version = "0.32.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 440 | dependencies = [ 441 | "memchr", 442 | ] 443 | 444 | [[package]] 445 | name = "once_cell" 446 | version = "1.18.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 449 | 450 | [[package]] 451 | name = "openssl" 452 | version = "0.10.59" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" 455 | dependencies = [ 456 | "bitflags 2.4.1", 457 | "cfg-if", 458 | "foreign-types", 459 | "libc", 460 | "once_cell", 461 | "openssl-macros", 462 | "openssl-sys", 463 | ] 464 | 465 | [[package]] 466 | name = "openssl-macros" 467 | version = "0.1.1" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 470 | dependencies = [ 471 | "proc-macro2", 472 | "quote", 473 | "syn", 474 | ] 475 | 476 | [[package]] 477 | name = "openssl-probe" 478 | version = "0.1.5" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 481 | 482 | [[package]] 483 | name = "openssl-sys" 484 | version = "0.9.95" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" 487 | dependencies = [ 488 | "cc", 489 | "libc", 490 | "pkg-config", 491 | "vcpkg", 492 | ] 493 | 494 | [[package]] 495 | name = "percent-encoding" 496 | version = "2.3.0" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 499 | 500 | [[package]] 501 | name = "pin-project-lite" 502 | version = "0.2.13" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 505 | 506 | [[package]] 507 | name = "pin-utils" 508 | version = "0.1.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 511 | 512 | [[package]] 513 | name = "pkg-config" 514 | version = "0.3.27" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 517 | 518 | [[package]] 519 | name = "proc-macro2" 520 | version = "1.0.69" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 523 | dependencies = [ 524 | "unicode-ident", 525 | ] 526 | 527 | [[package]] 528 | name = "quote" 529 | version = "1.0.33" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 532 | dependencies = [ 533 | "proc-macro2", 534 | ] 535 | 536 | [[package]] 537 | name = "reqwest" 538 | version = "0.11.22" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" 541 | dependencies = [ 542 | "base64", 543 | "bytes", 544 | "encoding_rs", 545 | "futures-core", 546 | "futures-util", 547 | "h2", 548 | "http", 549 | "http-body", 550 | "hyper", 551 | "hyper-tls", 552 | "ipnet", 553 | "js-sys", 554 | "log", 555 | "mime", 556 | "mime_guess", 557 | "native-tls", 558 | "once_cell", 559 | "percent-encoding", 560 | "pin-project-lite", 561 | "serde", 562 | "serde_json", 563 | "serde_urlencoded", 564 | "system-configuration", 565 | "tokio", 566 | "tokio-native-tls", 567 | "tower-service", 568 | "url", 569 | "wasm-bindgen", 570 | "wasm-bindgen-futures", 571 | "web-sys", 572 | "winreg", 573 | ] 574 | 575 | [[package]] 576 | name = "rustc-demangle" 577 | version = "0.1.23" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 580 | 581 | [[package]] 582 | name = "rustix" 583 | version = "0.38.32" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" 586 | dependencies = [ 587 | "bitflags 2.4.1", 588 | "errno", 589 | "libc", 590 | "linux-raw-sys", 591 | "windows-sys 0.52.0", 592 | ] 593 | 594 | [[package]] 595 | name = "rustygram" 596 | version = "0.1.4" 597 | dependencies = [ 598 | "dotenv", 599 | "reqwest", 600 | "serde", 601 | "serde_json", 602 | "tempfile", 603 | "tokio", 604 | ] 605 | 606 | [[package]] 607 | name = "ryu" 608 | version = "1.0.15" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 611 | 612 | [[package]] 613 | name = "schannel" 614 | version = "0.1.22" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" 617 | dependencies = [ 618 | "windows-sys 0.48.0", 619 | ] 620 | 621 | [[package]] 622 | name = "security-framework" 623 | version = "2.9.2" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" 626 | dependencies = [ 627 | "bitflags 1.3.2", 628 | "core-foundation", 629 | "core-foundation-sys", 630 | "libc", 631 | "security-framework-sys", 632 | ] 633 | 634 | [[package]] 635 | name = "security-framework-sys" 636 | version = "2.9.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" 639 | dependencies = [ 640 | "core-foundation-sys", 641 | "libc", 642 | ] 643 | 644 | [[package]] 645 | name = "serde" 646 | version = "1.0.193" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 649 | dependencies = [ 650 | "serde_derive", 651 | ] 652 | 653 | [[package]] 654 | name = "serde_derive" 655 | version = "1.0.193" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 658 | dependencies = [ 659 | "proc-macro2", 660 | "quote", 661 | "syn", 662 | ] 663 | 664 | [[package]] 665 | name = "serde_json" 666 | version = "1.0.108" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 669 | dependencies = [ 670 | "itoa", 671 | "ryu", 672 | "serde", 673 | ] 674 | 675 | [[package]] 676 | name = "serde_urlencoded" 677 | version = "0.7.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 680 | dependencies = [ 681 | "form_urlencoded", 682 | "itoa", 683 | "ryu", 684 | "serde", 685 | ] 686 | 687 | [[package]] 688 | name = "slab" 689 | version = "0.4.9" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 692 | dependencies = [ 693 | "autocfg", 694 | ] 695 | 696 | [[package]] 697 | name = "socket2" 698 | version = "0.4.10" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" 701 | dependencies = [ 702 | "libc", 703 | "winapi", 704 | ] 705 | 706 | [[package]] 707 | name = "socket2" 708 | version = "0.5.5" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 711 | dependencies = [ 712 | "libc", 713 | "windows-sys 0.48.0", 714 | ] 715 | 716 | [[package]] 717 | name = "syn" 718 | version = "2.0.39" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 721 | dependencies = [ 722 | "proc-macro2", 723 | "quote", 724 | "unicode-ident", 725 | ] 726 | 727 | [[package]] 728 | name = "system-configuration" 729 | version = "0.5.1" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 732 | dependencies = [ 733 | "bitflags 1.3.2", 734 | "core-foundation", 735 | "system-configuration-sys", 736 | ] 737 | 738 | [[package]] 739 | name = "system-configuration-sys" 740 | version = "0.5.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 743 | dependencies = [ 744 | "core-foundation-sys", 745 | "libc", 746 | ] 747 | 748 | [[package]] 749 | name = "tempfile" 750 | version = "3.10.1" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 753 | dependencies = [ 754 | "cfg-if", 755 | "fastrand", 756 | "rustix", 757 | "windows-sys 0.52.0", 758 | ] 759 | 760 | [[package]] 761 | name = "tinyvec" 762 | version = "1.6.0" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 765 | dependencies = [ 766 | "tinyvec_macros", 767 | ] 768 | 769 | [[package]] 770 | name = "tinyvec_macros" 771 | version = "0.1.1" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 774 | 775 | [[package]] 776 | name = "tokio" 777 | version = "1.34.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" 780 | dependencies = [ 781 | "backtrace", 782 | "bytes", 783 | "libc", 784 | "mio", 785 | "pin-project-lite", 786 | "socket2 0.5.5", 787 | "tokio-macros", 788 | "windows-sys 0.48.0", 789 | ] 790 | 791 | [[package]] 792 | name = "tokio-macros" 793 | version = "2.2.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 796 | dependencies = [ 797 | "proc-macro2", 798 | "quote", 799 | "syn", 800 | ] 801 | 802 | [[package]] 803 | name = "tokio-native-tls" 804 | version = "0.3.1" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 807 | dependencies = [ 808 | "native-tls", 809 | "tokio", 810 | ] 811 | 812 | [[package]] 813 | name = "tokio-util" 814 | version = "0.7.10" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 817 | dependencies = [ 818 | "bytes", 819 | "futures-core", 820 | "futures-sink", 821 | "pin-project-lite", 822 | "tokio", 823 | "tracing", 824 | ] 825 | 826 | [[package]] 827 | name = "tower-service" 828 | version = "0.3.2" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 831 | 832 | [[package]] 833 | name = "tracing" 834 | version = "0.1.40" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 837 | dependencies = [ 838 | "pin-project-lite", 839 | "tracing-core", 840 | ] 841 | 842 | [[package]] 843 | name = "tracing-core" 844 | version = "0.1.32" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 847 | dependencies = [ 848 | "once_cell", 849 | ] 850 | 851 | [[package]] 852 | name = "try-lock" 853 | version = "0.2.4" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 856 | 857 | [[package]] 858 | name = "unicase" 859 | version = "2.7.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 862 | dependencies = [ 863 | "version_check", 864 | ] 865 | 866 | [[package]] 867 | name = "unicode-bidi" 868 | version = "0.3.13" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 871 | 872 | [[package]] 873 | name = "unicode-ident" 874 | version = "1.0.12" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 877 | 878 | [[package]] 879 | name = "unicode-normalization" 880 | version = "0.1.22" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 883 | dependencies = [ 884 | "tinyvec", 885 | ] 886 | 887 | [[package]] 888 | name = "url" 889 | version = "2.4.1" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 892 | dependencies = [ 893 | "form_urlencoded", 894 | "idna", 895 | "percent-encoding", 896 | ] 897 | 898 | [[package]] 899 | name = "vcpkg" 900 | version = "0.2.15" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 903 | 904 | [[package]] 905 | name = "version_check" 906 | version = "0.9.4" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 909 | 910 | [[package]] 911 | name = "want" 912 | version = "0.3.1" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 915 | dependencies = [ 916 | "try-lock", 917 | ] 918 | 919 | [[package]] 920 | name = "wasi" 921 | version = "0.11.0+wasi-snapshot-preview1" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 924 | 925 | [[package]] 926 | name = "wasm-bindgen" 927 | version = "0.2.88" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" 930 | dependencies = [ 931 | "cfg-if", 932 | "wasm-bindgen-macro", 933 | ] 934 | 935 | [[package]] 936 | name = "wasm-bindgen-backend" 937 | version = "0.2.88" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" 940 | dependencies = [ 941 | "bumpalo", 942 | "log", 943 | "once_cell", 944 | "proc-macro2", 945 | "quote", 946 | "syn", 947 | "wasm-bindgen-shared", 948 | ] 949 | 950 | [[package]] 951 | name = "wasm-bindgen-futures" 952 | version = "0.4.38" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" 955 | dependencies = [ 956 | "cfg-if", 957 | "js-sys", 958 | "wasm-bindgen", 959 | "web-sys", 960 | ] 961 | 962 | [[package]] 963 | name = "wasm-bindgen-macro" 964 | version = "0.2.88" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" 967 | dependencies = [ 968 | "quote", 969 | "wasm-bindgen-macro-support", 970 | ] 971 | 972 | [[package]] 973 | name = "wasm-bindgen-macro-support" 974 | version = "0.2.88" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" 977 | dependencies = [ 978 | "proc-macro2", 979 | "quote", 980 | "syn", 981 | "wasm-bindgen-backend", 982 | "wasm-bindgen-shared", 983 | ] 984 | 985 | [[package]] 986 | name = "wasm-bindgen-shared" 987 | version = "0.2.88" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" 990 | 991 | [[package]] 992 | name = "web-sys" 993 | version = "0.3.65" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" 996 | dependencies = [ 997 | "js-sys", 998 | "wasm-bindgen", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "winapi" 1003 | version = "0.3.9" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1006 | dependencies = [ 1007 | "winapi-i686-pc-windows-gnu", 1008 | "winapi-x86_64-pc-windows-gnu", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "winapi-i686-pc-windows-gnu" 1013 | version = "0.4.0" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1016 | 1017 | [[package]] 1018 | name = "winapi-x86_64-pc-windows-gnu" 1019 | version = "0.4.0" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1022 | 1023 | [[package]] 1024 | name = "windows-sys" 1025 | version = "0.48.0" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1028 | dependencies = [ 1029 | "windows-targets 0.48.5", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "windows-sys" 1034 | version = "0.52.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1037 | dependencies = [ 1038 | "windows-targets 0.52.4", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "windows-targets" 1043 | version = "0.48.5" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1046 | dependencies = [ 1047 | "windows_aarch64_gnullvm 0.48.5", 1048 | "windows_aarch64_msvc 0.48.5", 1049 | "windows_i686_gnu 0.48.5", 1050 | "windows_i686_msvc 0.48.5", 1051 | "windows_x86_64_gnu 0.48.5", 1052 | "windows_x86_64_gnullvm 0.48.5", 1053 | "windows_x86_64_msvc 0.48.5", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "windows-targets" 1058 | version = "0.52.4" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 1061 | dependencies = [ 1062 | "windows_aarch64_gnullvm 0.52.4", 1063 | "windows_aarch64_msvc 0.52.4", 1064 | "windows_i686_gnu 0.52.4", 1065 | "windows_i686_msvc 0.52.4", 1066 | "windows_x86_64_gnu 0.52.4", 1067 | "windows_x86_64_gnullvm 0.52.4", 1068 | "windows_x86_64_msvc 0.52.4", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "windows_aarch64_gnullvm" 1073 | version = "0.48.5" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1076 | 1077 | [[package]] 1078 | name = "windows_aarch64_gnullvm" 1079 | version = "0.52.4" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 1082 | 1083 | [[package]] 1084 | name = "windows_aarch64_msvc" 1085 | version = "0.48.5" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1088 | 1089 | [[package]] 1090 | name = "windows_aarch64_msvc" 1091 | version = "0.52.4" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 1094 | 1095 | [[package]] 1096 | name = "windows_i686_gnu" 1097 | version = "0.48.5" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1100 | 1101 | [[package]] 1102 | name = "windows_i686_gnu" 1103 | version = "0.52.4" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 1106 | 1107 | [[package]] 1108 | name = "windows_i686_msvc" 1109 | version = "0.48.5" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1112 | 1113 | [[package]] 1114 | name = "windows_i686_msvc" 1115 | version = "0.52.4" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 1118 | 1119 | [[package]] 1120 | name = "windows_x86_64_gnu" 1121 | version = "0.48.5" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1124 | 1125 | [[package]] 1126 | name = "windows_x86_64_gnu" 1127 | version = "0.52.4" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 1130 | 1131 | [[package]] 1132 | name = "windows_x86_64_gnullvm" 1133 | version = "0.48.5" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1136 | 1137 | [[package]] 1138 | name = "windows_x86_64_gnullvm" 1139 | version = "0.52.4" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 1142 | 1143 | [[package]] 1144 | name = "windows_x86_64_msvc" 1145 | version = "0.48.5" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1148 | 1149 | [[package]] 1150 | name = "windows_x86_64_msvc" 1151 | version = "0.52.4" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 1154 | 1155 | [[package]] 1156 | name = "winreg" 1157 | version = "0.50.0" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 1160 | dependencies = [ 1161 | "cfg-if", 1162 | "windows-sys 0.48.0", 1163 | ] 1164 | --------------------------------------------------------------------------------