├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── flow.png ├── .idea ├── misc.xml ├── vcs.xml ├── modules.xml ├── tarkov.iml └── runConfigurations │ ├── Format.xml │ └── Check.xml ├── examples ├── auth_basic.rs ├── auth_2fa.rs ├── auth_captcha.rs ├── search_traders.rs ├── trader_sell_item.rs ├── ragfair_sell_item.rs ├── search_trader_items.rs ├── trader_barter_deal.rs ├── trader_cash_deal.rs └── ragfair_buy_item.rs ├── src ├── hwid.rs ├── friend.rs ├── bad_json.rs ├── market_filter.rs ├── inventory.rs ├── auth.rs ├── lib.rs ├── ragfair.rs ├── trading.rs ├── profile.rs └── constant.rs ├── Cargo.toml ├── LICENSE ├── README.md ├── .gitignore ├── tests └── test_api.rs └── Cargo.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: buymeacoff.ee/ddd 2 | -------------------------------------------------------------------------------- /flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dank/tarkov/HEAD/flow.png -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/tarkov.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/auth_basic.rs: -------------------------------------------------------------------------------- 1 | use tarkov::hwid::generate_hwid; 2 | use tarkov::{Error, Tarkov}; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), Error> { 6 | std::env::set_var("RUST_LOG", "tarkov=info"); 7 | env_logger::init(); 8 | 9 | let t = Tarkov::login("me@example.com", "password", generate_hwid().as_str()).await?; 10 | println!("{}", t.session); 11 | 12 | let t = Tarkov::from_access_token( 13 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", 14 | generate_hwid().as_str(), 15 | ) 16 | .await?; 17 | println!("{}", t.session); 18 | 19 | let t = Tarkov::from_session("e1bc65a216325f0ad0db8518fa299db1"); 20 | println!("{}", t.session); 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI (Linux) 2 | on: ["push", "pull_request"] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-18.04 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | - name: Install toolchain 11 | uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: stable 14 | target: x86_64-unknown-linux-gnu 15 | override: true 16 | - name: Run cargo fmt 17 | uses: actions-rs/cargo@v1 18 | with: 19 | command: fmt 20 | args: --all -- --check 21 | - name: Run cargo check 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: check 25 | args: --all --examples --tests -------------------------------------------------------------------------------- /.idea/runConfigurations/Format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Check.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /src/hwid.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | fn random_md5(rng: &mut R) -> String { 4 | format!("{:x}", md5::compute(&rng.gen::().to_le_bytes())) 5 | } 6 | 7 | /// Generate a random EFT compatible HWID. 8 | pub fn generate_hwid() -> String { 9 | let mut rng = rand::thread_rng(); 10 | 11 | let short_md5 = { 12 | let mut hash = random_md5(&mut rng); 13 | hash.truncate(hash.len() - 8); 14 | 15 | hash 16 | }; 17 | 18 | format!( 19 | "#1-{}:{}:{}-{}-{}-{}-{}-{}", 20 | random_md5(&mut rng), 21 | random_md5(&mut rng), 22 | random_md5(&mut rng), 23 | random_md5(&mut rng), 24 | random_md5(&mut rng), 25 | random_md5(&mut rng), 26 | random_md5(&mut rng), 27 | short_md5 28 | ) 29 | } 30 | 31 | #[test] 32 | fn test_generate_hwid() { 33 | assert_eq!(generate_hwid().len(), 258) 34 | } 35 | -------------------------------------------------------------------------------- /examples/auth_2fa.rs: -------------------------------------------------------------------------------- 1 | use tarkov::auth::LoginError; 2 | use tarkov::hwid::generate_hwid; 3 | use tarkov::{Error, Tarkov}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Error> { 7 | std::env::set_var("RUST_LOG", "tarkov=info"); 8 | env_logger::init(); 9 | 10 | let email = "me@example.com"; 11 | let password = "password"; 12 | let hwid = generate_hwid(); 13 | 14 | let t = match Tarkov::login(email, password, &hwid).await { 15 | Ok(t) => Ok(t), 16 | Err(Error::LoginError(e)) => match e { 17 | // 2FA required! 18 | LoginError::TwoFactorRequired => { 19 | // Get 2FA from email (or generate TOTP) then continue... 20 | let code = "XYZ"; 21 | Tarkov::login_with_2fa(email, password, code, &hwid).await 22 | } 23 | _ => Err(e)?, 24 | }, 25 | Err(e) => Err(e), 26 | }?; 27 | 28 | println!("{}", t.session); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tarkov" 3 | description = "A client library for the Escape from Tarkov API" 4 | version = "0.1.6" 5 | authors = ["Dan Kyung "] 6 | edition = "2018" 7 | readme = "README.md" 8 | license = "MIT" 9 | keywords = ["escapefromtarkov", "eft", "tarkov", "sdk", "api"] 10 | include = ["src/**/*", "Cargo.toml", "LICENSE"] 11 | repository = "https://github.com/dank/tarkov" 12 | documentation = "https://docs.rs/tarkov" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | md5 = "0.7" 18 | err-derive = "0.2" 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | flate2 = { version = "1.0", features = ["zlib"], default-features = false } 22 | rand = "0.7" 23 | log = "0.4" 24 | serde_repr = "0.1" 25 | hyper = "0.13" 26 | hyper-tls = "0.4" 27 | http = "0.2" 28 | 29 | [dev-dependencies] 30 | env_logger = "0.7" 31 | tokio = { version = "0.2", features = ["macros"] } 32 | -------------------------------------------------------------------------------- /examples/auth_captcha.rs: -------------------------------------------------------------------------------- 1 | use tarkov::auth::LoginError; 2 | use tarkov::hwid::generate_hwid; 3 | use tarkov::{Error, Tarkov}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Error> { 7 | std::env::set_var("RUST_LOG", "tarkov=info"); 8 | env_logger::init(); 9 | 10 | let email = "me@example.com"; 11 | let password = "password"; 12 | let hwid = generate_hwid(); 13 | 14 | let t = match Tarkov::login(email, password, &hwid).await { 15 | Ok(t) => Ok(t), 16 | Err(Error::LoginError(e)) => match e { 17 | // Captcha required! 18 | LoginError::CaptchaRequired => { 19 | // Solve captcha here and try again 20 | let captcha = "03AOLTBLQ952pO-qQYPeLr53N5nK9Co14iXyCp..."; 21 | Tarkov::login_with_captcha(email, password, captcha, &hwid).await 22 | } 23 | _ => Err(e)?, 24 | }, 25 | Err(e) => Err(e), 26 | }?; 27 | 28 | println!("{}", t.session); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /examples/search_traders.rs: -------------------------------------------------------------------------------- 1 | use tarkov::profile::Side; 2 | use tarkov::{Error, Tarkov}; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), Error> { 6 | std::env::set_var("RUST_LOG", "tarkov=info"); 7 | env_logger::init(); 8 | 9 | let t = Tarkov::from_session("e1bc65a216325f0ad0db8518fa299db2"); 10 | 11 | // Find and select PMC profile to complete login. 12 | let profiles = t.get_profiles().await?; 13 | let profile = profiles 14 | .into_iter() 15 | .find(|p| p.info.side != Side::Savage) 16 | .unwrap(); 17 | t.select_profile(&profile.id).await?; 18 | 19 | // Fetch the translation table because trader names are in Russian. 20 | let locale = t.get_i18n("en").await?; 21 | 22 | // Find trader by name. 23 | let traders = t.get_traders().await?; 24 | let trader = traders 25 | .into_iter() 26 | .find(|t| locale.traders.get(&t.id).unwrap().nickname == "Mechanic") 27 | .unwrap(); 28 | 29 | println!("{:#?}", trader); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dan Kyung 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. -------------------------------------------------------------------------------- /examples/trader_sell_item.rs: -------------------------------------------------------------------------------- 1 | use tarkov::profile::Side; 2 | use tarkov::{Error, Tarkov}; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), Error> { 6 | std::env::set_var("RUST_LOG", "tarkov=info"); 7 | env_logger::init(); 8 | 9 | let t = Tarkov::from_session("da3902b29e442da4972f8ce499834ee7"); 10 | 11 | // Find and select PMC profile to complete login. 12 | let profiles = t.get_profiles().await?; 13 | let profile = profiles 14 | .into_iter() 15 | .find(|p| p.info.side != Side::Savage) 16 | .unwrap(); 17 | t.select_profile(&profile.id).await?; 18 | 19 | // Get Therapist 20 | let traders = t.get_traders().await?; 21 | let trader = traders 22 | .into_iter() 23 | .find(|t| t.nickname == "Терапевт") 24 | .unwrap(); 25 | 26 | // Get painkiller item ID from my inventory 27 | let painkiller = &profile 28 | .inventory 29 | .items 30 | .into_iter() 31 | .find(|i| i.schema_id == "544fb37f4bdc2dee738b4567") 32 | .unwrap(); 33 | 34 | // Sell item 35 | println!("{:#?}", t.sell_item(&trader.id, &painkiller.id, 1).await); 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/ragfair_sell_item.rs: -------------------------------------------------------------------------------- 1 | use tarkov::profile::Side; 2 | use tarkov::ragfair::Requirement; 3 | use tarkov::{Error, Tarkov}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Error> { 7 | std::env::set_var("RUST_LOG", "tarkov=info"); 8 | env_logger::init(); 9 | 10 | let t = Tarkov::from_session("e1bc65a216325f0ad0db8518fa299db2"); 11 | 12 | // Find and select PMC profile to complete login. 13 | let profiles = t.get_profiles().await?; 14 | let profile = profiles 15 | .into_iter() 16 | .find(|p| p.info.side != Side::Savage) 17 | .unwrap(); 18 | t.select_profile(&profile.id).await?; 19 | 20 | // Get the painkiller item ID from my inventory 21 | let painkiller = &profile 22 | .inventory 23 | .items 24 | .into_iter() 25 | .find(|i| i.schema_id == "544fb37f4bdc2dee738b4567") 26 | .unwrap(); 27 | 28 | // Get market price of painkiller 29 | let price = t.get_item_price(&painkiller.schema_id).await?; 30 | 31 | // Put the painkiller on market for average price 32 | println!( 33 | "{:#?}", 34 | t.offer_item( 35 | &[&painkiller.id], 36 | &[Requirement { 37 | schema_id: "5449016a4bdc2d6f028b456f".to_string(), 38 | count: price.avg.round(), 39 | }], 40 | false, 41 | ) 42 | .await? 43 | ); 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /examples/search_trader_items.rs: -------------------------------------------------------------------------------- 1 | use tarkov::profile::Side; 2 | use tarkov::{Error, Tarkov}; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), Error> { 6 | std::env::set_var("RUST_LOG", "tarkov=info"); 7 | env_logger::init(); 8 | 9 | let t = Tarkov::from_session("e1bc65a216325f0ad0db8518fa299db2"); 10 | 11 | // Find and select PMC profile to complete login. 12 | let profiles = t.get_profiles().await?; 13 | let profile = profiles 14 | .into_iter() 15 | .find(|p| p.info.side != Side::Savage) 16 | .unwrap(); 17 | t.select_profile(&profile.id).await?; 18 | 19 | // Fetch the translation table because trader names are in Russian. 20 | let locale = t.get_i18n("en").await?; 21 | 22 | // Find trader by name. 23 | let traders = t.get_traders().await?; 24 | let trader = traders 25 | .into_iter() 26 | .find(|t| locale.traders.get(&t.id).unwrap().nickname == "Therapist") 27 | .unwrap(); 28 | 29 | // Fetch all items to map item ID to item name/descriptor. 30 | let items = t.get_items().await?; 31 | 32 | // Find trader's item by name 33 | let trader_items = t.get_trader_items(&trader.id).await?; 34 | let painkiller = trader_items 35 | .into_iter() 36 | .find(|i| { 37 | // Therapist sells painkillers for Roubles or 2 matches. Finding the barter deal. 38 | let item = items.get(&i.schema_id).unwrap(); 39 | item.name == "painkiller" && i.price.get(0).unwrap().count == 2.0 40 | }) 41 | .unwrap(); 42 | 43 | println!("{:#?}", painkiller); 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /src/friend.rs: -------------------------------------------------------------------------------- 1 | use crate::{handle_error, ErrorResponse, Result, Tarkov, PROD_ENDPOINT}; 2 | 3 | use crate::profile::Side; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Debug, Deserialize)] 7 | struct FriendResponse { 8 | #[serde(flatten)] 9 | error: ErrorResponse, 10 | data: Option, 11 | } 12 | 13 | /// Friend list 14 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 15 | #[serde(rename_all = "PascalCase")] 16 | pub struct Friends { 17 | /// Friends 18 | pub friends: Vec, 19 | /// Muted friend IDs 20 | pub ignore: Vec, 21 | /// ? 22 | pub in_ignore_list: Vec, 23 | } 24 | 25 | /// Friend 26 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 27 | #[serde(rename_all = "PascalCase")] 28 | pub struct Friend { 29 | /// Friend ID 30 | #[serde(rename = "_id")] 31 | pub id: String, 32 | /// Friend info 33 | pub info: Info, 34 | } 35 | 36 | /// Friend info 37 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 38 | #[serde(rename_all = "PascalCase")] 39 | pub struct Info { 40 | /// Friend nickname 41 | pub nickname: String, 42 | /// Friend side 43 | pub side: Side, 44 | /// Friend level 45 | pub level: u64, 46 | /// ? 47 | pub member_category: String, 48 | } 49 | 50 | impl Tarkov { 51 | /// Get a list of account's friends. 52 | pub async fn get_friends(&self) -> Result { 53 | let url = format!("{}/client/friend/list", PROD_ENDPOINT); 54 | let res: FriendResponse = self.post_json(&url, &{}).await?; 55 | 56 | handle_error(res.error, res.data) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/trader_barter_deal.rs: -------------------------------------------------------------------------------- 1 | use tarkov::inventory::BarterItem; 2 | use tarkov::profile::Side; 3 | use tarkov::{Error, Tarkov}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Error> { 7 | std::env::set_var("RUST_LOG", "tarkov=info"); 8 | env_logger::init(); 9 | 10 | let t = Tarkov::from_session("e1bc65a216325f0ad0db8518fa299db2"); 11 | 12 | // Find and select PMC profile to complete login. 13 | let profiles = t.get_profiles().await?; 14 | let profile = profiles 15 | .into_iter() 16 | .find(|p| p.info.side != Side::Savage) 17 | .unwrap(); 18 | t.select_profile(&profile.id).await?; 19 | 20 | // Get Therapist 21 | let traders = t.get_traders().await?; 22 | let trader = traders 23 | .into_iter() 24 | .find(|t| t.nickname == "Терапевт") 25 | .unwrap(); 26 | 27 | // Therapist wants 2 matches for 1 painkiller. 28 | let painkiller = t 29 | .get_trader_items(&trader.id) 30 | .await? 31 | .into_iter() 32 | .find(|i| i.id == "5e064f5deb009468d90baf01") 33 | .unwrap(); 34 | 35 | // Get the IDs of 2 painkillers from my inventory 36 | let barter_items = &profile 37 | .inventory 38 | .items 39 | .into_iter() 40 | .filter(|i| i.schema_id == painkiller.price.get(0).unwrap().schema_id) 41 | .map(|i| BarterItem { 42 | id: i.id, 43 | count: 1.0, 44 | }) 45 | .collect::>()[..2]; 46 | 47 | // Trade item 48 | println!( 49 | "{:#?}", 50 | t.trade_item(&trader.id, &painkiller.id, 1, barter_items) 51 | .await? 52 | ); 53 | 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /examples/trader_cash_deal.rs: -------------------------------------------------------------------------------- 1 | use tarkov::inventory::BarterItem; 2 | use tarkov::profile::Side; 3 | use tarkov::{Error, Tarkov}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Error> { 7 | std::env::set_var("RUST_LOG", "tarkov=info"); 8 | env_logger::init(); 9 | 10 | let t = Tarkov::from_session("3e463058dd4884ab0c4a6035dc56066b"); 11 | 12 | // Find and select PMC profile to complete login. 13 | let profiles = t.get_profiles().await?; 14 | let profile = profiles 15 | .into_iter() 16 | .find(|p| p.info.side != Side::Savage) 17 | .unwrap(); 18 | t.select_profile(&profile.id).await?; 19 | 20 | // Get Therapist 21 | let traders = t.get_traders().await?; 22 | let trader = traders 23 | .into_iter() 24 | .find(|t| t.nickname == "Терапевт") 25 | .unwrap(); 26 | 27 | // Therapist wants 3990₽ for 1 painkiller. 28 | let painkiller = t 29 | .get_trader_items(&trader.id) 30 | .await? 31 | .into_iter() 32 | .find(|i| i.id == "5e064f5deb009468d90baef7") 33 | .unwrap(); 34 | 35 | // Get Rouble item ID from my inventory 36 | let rouble = &profile 37 | .inventory 38 | .items 39 | .into_iter() 40 | .find(|i| i.schema_id == "5449016a4bdc2d6f028b456f") 41 | .unwrap(); 42 | 43 | // Trade item 44 | println!( 45 | "{:#?}", 46 | t.trade_item( 47 | &trader.id, 48 | "5e064f5deb009468d90baef7", 49 | 1, 50 | &[BarterItem { 51 | id: rouble.id.to_owned(), 52 | count: painkiller.price.get(0).unwrap().count, // Assume price is 3990₽. 53 | }], 54 | ) 55 | .await 56 | ); 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /examples/ragfair_buy_item.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | use tarkov::inventory::BarterItem; 3 | use tarkov::market_filter::{Currency, MarketFilter, Owner}; 4 | use tarkov::profile::Side; 5 | use tarkov::{Error, Tarkov}; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), Error> { 9 | std::env::set_var("RUST_LOG", "tarkov=info"); 10 | env_logger::init(); 11 | 12 | let t = Tarkov::from_session("e1bc65a216325f0ad0db8518fa299db2"); 13 | 14 | // Find and select PMC profile to complete login. 15 | let profiles = t.get_profiles().await?; 16 | let profile = profiles 17 | .into_iter() 18 | .find(|p| p.info.side != Side::Savage) 19 | .unwrap(); 20 | t.select_profile(&profile.id).await?; 21 | 22 | // Fetch the translation table for market categories. 23 | let locale = t.get_i18n("en").await?; 24 | 25 | // Get the "Barter items" category ID. 26 | let barter_item_category_id: Option = { 27 | let mut id = None; 28 | for (k, v) in locale.handbook { 29 | if v == "Barter items" { 30 | id = Some(k); 31 | break; 32 | } 33 | } 34 | 35 | id 36 | }; 37 | 38 | // Search flea market. 39 | let offers = t 40 | .search_market( 41 | 0, 42 | 15, 43 | MarketFilter { 44 | max_price: Some(2000), 45 | handbook_id: barter_item_category_id.as_deref(), 46 | owner_type: Owner::Player, 47 | hide_bartering_offers: true, 48 | currency: Currency::Rouble, 49 | ..MarketFilter::default() 50 | }, 51 | ) 52 | .await?; 53 | 54 | // Find the first item available for purchase immediately. 55 | let current_time = SystemTime::now(); 56 | let epoch_time = current_time.duration_since(UNIX_EPOCH).unwrap().as_secs(); 57 | let offer = offers 58 | .offers 59 | .into_iter() 60 | .find(|o| o.start_time + 60 <= epoch_time && o.end_time >= epoch_time) 61 | .unwrap(); 62 | 63 | // Get Rouble item ID from my inventory 64 | let rouble = &profile 65 | .inventory 66 | .items 67 | .into_iter() 68 | .find(|i| i.schema_id == "5449016a4bdc2d6f028b456f") 69 | .unwrap(); 70 | 71 | // Buy the item. 72 | println!( 73 | "{:#?}", 74 | t.buy_item( 75 | &offer.id, 76 | 1, 77 | &[BarterItem { 78 | id: rouble.id.to_owned(), 79 | count: offer.requirements_cost as f64, 80 | }], 81 | ) 82 | .await? 83 | ); 84 | 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tarkov 2 | [![Crates.io](https://img.shields.io/crates/v/tarkov)](https://crates.io/crates/tarkov) 3 | [![Documentation](https://docs.rs/tarkov/badge.svg)](https://docs.rs/tarkov) 4 | [![Crates.io](https://img.shields.io/crates/l/tarkov)](LICENSE) 5 | 6 | An unofficial client library for the [Escape from Tarkov](https://escapefromtarkov.com) (EFT) API. 7 | 8 | Note: The game updates faster than I can update this crate. Make sure the `GAME_VERSION` and `LAUNCHER_VERSION` consts are up to date. 9 | 10 | ## Features 11 | - [x] Authentication 12 | - [x] Flea market 13 | - [x] Traders 14 | - [ ] Hideout 15 | - [ ] Inventory management (equip, move, delete, etc) 16 | - [ ] Messenger 17 | - [ ] Quests 18 | 19 | ## Getting Started 20 | 21 | Comprehensive examples can be found in the [`examples`](examples) directory. 22 | 23 | ### Usage 24 | Add this to your `Cargo.toml`: 25 | ``` 26 | [dependencies] 27 | tarkov = "0.1" 28 | ``` 29 | 30 | ### Authentication 31 | ![Authentication flowchart](flow.png) 32 | There are three ways to authenticate your EFT account for `tarkov`: 33 | 1. Email & password is the easiest way to authenticate your account. However, a captcha and 2FA code may be required. Read the [HWID section](#hardware-id) for more details. 34 | 2. Access token or Bearer token can be found by sniffing EFT launcher traffic. HWID from the launcher is required. 35 | 3. Session is a cookie called `PHPSESSID`, it can be found by sniffing EFT launcher traffic. HWID is not required for this method. 36 | 37 | **Your _PMC_ character profile must be selected with `select_profile` to complete the authentication.** 38 | 39 | ### Hardware ID 40 | Hardware ID (HWID) may be required on authentication, it can either be sniffed from the EFT launcher or generated. It's recommended to save the HWID in a _persistent store_ and reuse it after the first successful authentication. 41 | 42 | Using a fresh HWID means both captcha and 2FA code will be required on your first login attempt. This can be avoid by using the HWID generated by the EFT launcher or authenticating with your session cookie. 43 | 44 | ### Captcha 45 | This library does not attempt to solve captchas for you, the `g-recaptcha-response` token from reCAPTCHA may be required on authentication. 46 | 47 | reCAPTCHA can be solved externally using tools like [captcha-harvester](https://github.com/dzt/captcha-harvester). 48 | 49 | ### Rust Version 50 | `tarkov` has a minimum version requirement of `1.40`. 51 | 52 | ## "Unofficial" 53 | 54 | I should emphasize that this library is _unofficial_. EFT does not have a public API, everything in this repo was reversed from the game. 55 | 56 | The API is clearly designed for internal use. It contains numerous spelling mistakes, inconsistent conventions, and tons of bad practice JSON. The developers may push breaking changes without prior warning. 57 | 58 | ## License 59 | [MIT](LICENSE) 60 | -------------------------------------------------------------------------------- /src/bad_json.rs: -------------------------------------------------------------------------------- 1 | //! EFT API returns a lot of inconsistent and bad JSON. Serde deserializers to fix those broken JSON. 2 | 3 | use crate::inventory::Location; 4 | use serde::de::{Error, Visitor}; 5 | use serde::{Deserialize, Deserializer, Serialize}; 6 | use serde_json::Value; 7 | use std::fmt; 8 | 9 | pub(crate) fn deserialize_integer_to_string<'de, D>(de: D) -> Result 10 | where 11 | D: Deserializer<'de>, 12 | { 13 | let json: serde_json::Value = Deserialize::deserialize(de)?; 14 | match json { 15 | Value::Number(i) => Ok(i.to_string()), 16 | Value::String(s) => Ok(s), 17 | _ => Err(Error::custom("Unexpected value")), 18 | } 19 | } 20 | 21 | pub(crate) fn deserialize_integer_to_option_string<'de, D>( 22 | de: D, 23 | ) -> Result, D::Error> 24 | where 25 | D: Deserializer<'de>, 26 | { 27 | let json: serde_json::Value = Deserialize::deserialize(de)?; 28 | match json { 29 | Value::Number(i) => Ok(Some(i.to_string())), 30 | Value::String(s) => Ok(Some(s)), 31 | _ => Ok(None), 32 | } 33 | } 34 | 35 | pub(crate) fn deserialize_bad_location_as_none<'de, D>(de: D) -> Result, D::Error> 36 | where 37 | D: Deserializer<'de>, 38 | { 39 | let json: serde_json::Value = Deserialize::deserialize(de)?; 40 | match json { 41 | Value::Object(_) => Ok(Some( 42 | serde_json::from_value(json).map_err(|_| Error::custom("Unexpected value"))?, 43 | )), 44 | _ => Ok(None), 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone, PartialEq, Serialize)] 49 | pub struct StringOrInt(String); 50 | 51 | impl<'de> Deserialize<'de> for StringOrInt { 52 | fn deserialize(deserializer: D) -> Result 53 | where 54 | D: Deserializer<'de>, 55 | { 56 | struct StringOrIntVisitor; 57 | 58 | impl<'de> Visitor<'de> for StringOrIntVisitor { 59 | type Value = StringOrInt; 60 | 61 | fn expecting(&self, w: &mut fmt::Formatter) -> fmt::Result { 62 | write!(w, "string or integer represented as String.") 63 | } 64 | 65 | fn visit_i64(self, v: i64) -> Result 66 | where 67 | E: serde::de::Error, 68 | { 69 | Ok(StringOrInt(v.to_string())) 70 | } 71 | 72 | fn visit_u64(self, v: u64) -> Result 73 | where 74 | E: serde::de::Error, 75 | { 76 | Ok(StringOrInt(v.to_string())) 77 | } 78 | 79 | fn visit_str(self, v: &str) -> Result 80 | where 81 | E: serde::de::Error, 82 | { 83 | Ok(StringOrInt(v.to_string())) 84 | } 85 | } 86 | 87 | deserializer.deserialize_any(StringOrIntVisitor) 88 | } 89 | } 90 | 91 | impl StringOrInt { 92 | pub fn as_ref(&self) -> &str { 93 | self.0.as_str() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/rust,macos,intellij 2 | # Edit at https://www.gitignore.io/?templates=rust,macos,intellij 3 | 4 | ### Intellij ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/modules.xml 36 | # .idea/*.iml 37 | # .idea/modules 38 | # *.iml 39 | # *.ipr 40 | 41 | # CMake 42 | cmake-build-*/ 43 | 44 | # Mongo Explorer plugin 45 | .idea/**/mongoSettings.xml 46 | 47 | # File-based project format 48 | *.iws 49 | 50 | # IntelliJ 51 | out/ 52 | 53 | # mpeltonen/sbt-idea plugin 54 | .idea_modules/ 55 | 56 | # JIRA plugin 57 | atlassian-ide-plugin.xml 58 | 59 | # Cursive Clojure plugin 60 | .idea/replstate.xml 61 | 62 | # Crashlytics plugin (for Android Studio and IntelliJ) 63 | com_crashlytics_export_strings.xml 64 | crashlytics.properties 65 | crashlytics-build.properties 66 | fabric.properties 67 | 68 | # Editor-based Rest Client 69 | .idea/httpRequests 70 | 71 | # Android studio 3.1+ serialized cache file 72 | .idea/caches/build_file_checksums.ser 73 | 74 | ### Intellij Patch ### 75 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 76 | 77 | # *.iml 78 | # modules.xml 79 | # .idea/misc.xml 80 | # *.ipr 81 | 82 | # Sonarlint plugin 83 | .idea/**/sonarlint/ 84 | 85 | # SonarQube Plugin 86 | .idea/**/sonarIssues.xml 87 | 88 | # Markdown Navigator plugin 89 | .idea/**/markdown-navigator.xml 90 | .idea/**/markdown-navigator/ 91 | 92 | ### macOS ### 93 | # General 94 | .DS_Store 95 | .AppleDouble 96 | .LSOverride 97 | 98 | # Icon must end with two \r 99 | Icon 100 | 101 | # Thumbnails 102 | ._* 103 | 104 | # Files that might appear in the root of a volume 105 | .DocumentRevisions-V100 106 | .fseventsd 107 | .Spotlight-V100 108 | .TemporaryItems 109 | .Trashes 110 | .VolumeIcon.icns 111 | .com.apple.timemachine.donotpresent 112 | 113 | # Directories potentially created on remote AFP share 114 | .AppleDB 115 | .AppleDesktop 116 | Network Trash Folder 117 | Temporary Items 118 | .apdisk 119 | 120 | ### Rust ### 121 | # Generated by Cargo 122 | # will have compiled files and executables 123 | /target/ 124 | 125 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 126 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 127 | # Cargo.lock 128 | 129 | # These are backup files generated by rustfmt 130 | **/*.rs.bk 131 | 132 | # End of https://www.gitignore.io/api/rust,macos,intellij -------------------------------------------------------------------------------- /src/market_filter.rs: -------------------------------------------------------------------------------- 1 | use serde_repr::Serialize_repr; 2 | 3 | /// Search filter for the flea market. 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub struct MarketFilter<'a> { 6 | /// Sort type. 7 | pub sort_type: SortBy, 8 | /// Sort direction. 9 | pub sort_direction: SortDirection, 10 | /// Offer currency type. 11 | pub currency: Currency, 12 | /// Minimum item price. 13 | pub min_price: Option, 14 | /// Maximum item price. 15 | pub max_price: Option, 16 | /// Minimum item quantity. 17 | pub min_quantity: Option, 18 | /// Maximum item quantity. 19 | pub max_quantity: Option, 20 | /// Minimum item condition percentage. 21 | pub min_condition: Option, 22 | /// Maximum item condition percentage. 23 | pub max_condition: Option, 24 | /// Show offers expiring soon. 25 | pub expiring_within_hour: bool, 26 | /// Hide offers asking for items for trade. 27 | pub hide_bartering_offers: bool, 28 | /// Offer owner type. 29 | pub owner_type: Owner, 30 | /// Hide inoperable weapons. 31 | pub hide_inoperable_weapons: bool, 32 | /// Search by market category or item ID. 33 | pub handbook_id: Option<&'a str>, 34 | /// Search item related to item ID. 35 | pub linked_search_id: Option<&'a str>, 36 | /// Search items that can be traded for item ID. 37 | pub required_search_id: Option<&'a str>, 38 | } 39 | 40 | /// Sort by categories. 41 | #[derive(Serialize_repr, Debug, Clone, PartialEq)] 42 | #[repr(u8)] 43 | pub enum SortBy { 44 | /// Sort by ID 45 | ID = 0, 46 | /// Sort by bartering offers 47 | BarteringOffers = 2, 48 | /// Sort by merchant rating 49 | MerchantRating = 3, 50 | /// Sort by price (default) 51 | Price = 5, 52 | /// Sort by expiry 53 | Expiry = 6, 54 | } 55 | 56 | /// Sort by direction. 57 | #[derive(Serialize_repr, Debug, Clone, PartialEq)] 58 | #[repr(u8)] 59 | pub enum SortDirection { 60 | /// Sort ascending (default) 61 | Ascending = 0, 62 | /// Sort descending 63 | Descending = 1, 64 | } 65 | 66 | /// Currency types. 67 | #[derive(Serialize_repr, Debug, Clone, PartialEq)] 68 | #[repr(u8)] 69 | pub enum Currency { 70 | /// Any currency (default) 71 | Any = 0, 72 | /// Rouble 73 | Rouble = 1, 74 | /// US dollar 75 | Dollar = 2, 76 | /// Euro 77 | Euro = 3, 78 | } 79 | 80 | /// Item listed by. 81 | #[derive(Serialize_repr, Debug, Clone, PartialEq)] 82 | #[repr(u8)] 83 | pub enum Owner { 84 | /// Any owner (default) 85 | Any = 0, 86 | /// Item listed by traders 87 | Traders = 1, 88 | /// Item listed by players 89 | Player = 2, 90 | } 91 | 92 | impl<'a> Default for MarketFilter<'a> { 93 | fn default() -> Self { 94 | Self { 95 | sort_type: SortBy::Price, 96 | sort_direction: SortDirection::Ascending, 97 | currency: Currency::Any, 98 | min_price: None, 99 | max_price: None, 100 | min_quantity: None, 101 | max_quantity: None, 102 | min_condition: None, 103 | max_condition: Some(100), 104 | expiring_within_hour: false, 105 | hide_bartering_offers: false, 106 | owner_type: Owner::Any, 107 | hide_inoperable_weapons: true, 108 | handbook_id: None, 109 | linked_search_id: None, 110 | required_search_id: None, 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/inventory.rs: -------------------------------------------------------------------------------- 1 | use crate::bad_json::deserialize_bad_location_as_none; 2 | use crate::ErrorResponse; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Serialize)] 6 | pub(crate) struct MoveItemRequest<'a, T> { 7 | pub(crate) data: &'a [T], 8 | pub(crate) tm: u64, 9 | } 10 | 11 | #[derive(Debug, Deserialize)] 12 | #[serde(rename_all = "camelCase")] 13 | pub(crate) struct RagfairResponseData { 14 | pub(crate) items: serde_json::Value, 15 | #[serde(rename = "badRequest")] 16 | pub(crate) errors: Vec, 17 | } 18 | 19 | /// Changes to the player's inventory after interacting with traders or the flea market. 20 | #[derive(Debug, Deserialize, Serialize)] 21 | #[serde(rename_all = "camelCase")] 22 | pub struct InventoryUpdate { 23 | /// New items in inventory. 24 | pub new: Option>, 25 | /// Changed items in inventory. 26 | pub change: Option>, 27 | /// Deleted items in inventory. 28 | pub del: Option>, 29 | } 30 | 31 | /// Item deleted from inventory. 32 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 33 | #[serde(rename_all = "camelCase")] 34 | pub struct DeletedItem { 35 | /// Item ID 36 | #[serde(rename = "_id")] 37 | pub id: String, 38 | } 39 | 40 | /// In-game item 41 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 42 | #[serde(rename_all = "camelCase")] 43 | pub struct Item { 44 | /// Item ID 45 | #[serde(rename = "_id")] 46 | pub id: String, 47 | /// Item localization schema ID 48 | #[serde(rename = "_tpl")] 49 | pub schema_id: String, 50 | /// Item parent ID 51 | pub parent_id: Option, 52 | /// Item slot ID 53 | pub slot_id: Option, 54 | /// Item attachments/options 55 | pub upd: Option, 56 | /// Item location 57 | #[serde(default, deserialize_with = "deserialize_bad_location_as_none")] 58 | pub location: Option, 59 | } 60 | 61 | /// Item location 62 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 63 | #[serde(rename_all = "camelCase")] 64 | pub struct Location { 65 | /// Inventory slot x 66 | pub x: u64, 67 | /// Inventory slot y 68 | pub y: u64, 69 | /// Inventory slot rotation 70 | pub r: u64, 71 | /// Item is searched (if searchable) 72 | pub is_searched: Option, 73 | } 74 | 75 | /// Item options 76 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 77 | #[serde(rename_all = "PascalCase")] 78 | pub struct Upd { 79 | /// Item stack count 80 | pub stack_objects_count: Option, 81 | /// Item spawned in session 82 | pub spawned_in_session: Option, 83 | /// Item is medkit 84 | pub med_kit: Option, 85 | /// Item is repairable 86 | pub repairable: Option, 87 | /// Item has a light attachment 88 | pub light: Option, 89 | /// Unlimited stack 90 | pub unlimited_count: Option, 91 | /// ? 92 | pub buy_restriction_max: Option, 93 | /// ? 94 | pub buy_restriction_current: Option, 95 | /// Key info 96 | pub key: Option, 97 | /// Tag info 98 | pub tag: Option, 99 | } 100 | 101 | /// Medkit item info 102 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 103 | #[serde(rename_all = "PascalCase")] 104 | pub struct UpdMedkit { 105 | /// Health 106 | pub hp_resource: f64, 107 | } 108 | 109 | /// Repairable item info 110 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 111 | #[serde(rename_all = "PascalCase")] 112 | pub struct UpdRepairable { 113 | /// Maximum durability 114 | pub max_durability: Option, 115 | /// Current durability 116 | pub durability: f64, 117 | } 118 | 119 | /// Light attachment info 120 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 121 | #[serde(rename_all = "PascalCase")] 122 | pub struct UpdLight { 123 | /// Light is active 124 | pub is_active: bool, 125 | /// Light mode 126 | pub selected_mode: u64, 127 | } 128 | 129 | /// Key info 130 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 131 | #[serde(rename_all = "PascalCase")] 132 | pub struct UpdKey { 133 | /// Number of usage 134 | pub number_of_usages: u64, 135 | } 136 | 137 | /// Tag info 138 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 139 | #[serde(rename_all = "PascalCase")] 140 | pub struct UpdTag { 141 | /// Color 142 | pub color: u64, 143 | /// Name 144 | pub name: String, 145 | } 146 | 147 | /// Inventory item for trading. 148 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 149 | pub struct BarterItem { 150 | /// Item ID from player's inventory. 151 | pub id: String, 152 | /// Amount of items. 153 | pub count: f64, 154 | } 155 | -------------------------------------------------------------------------------- /tests/test_api.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::time::{SystemTime, UNIX_EPOCH}; 3 | use tarkov::inventory::BarterItem; 4 | use tarkov::market_filter::{Currency, MarketFilter, Owner}; 5 | use tarkov::profile::Side; 6 | use tarkov::ragfair::Requirement; 7 | use tarkov::{Result, Tarkov}; 8 | 9 | #[tokio::test] 10 | async fn test_profile() -> Result<()> { 11 | let session = env::var("TARKOV_SESSION").unwrap(); 12 | let t = Tarkov::from_session(&session); 13 | 14 | let profiles = t.get_profiles().await?; 15 | let profile = profiles 16 | .into_iter() 17 | .find(|p| p.info.side != Side::Savage) 18 | .unwrap(); 19 | 20 | t.select_profile(&profile.id).await?; 21 | 22 | Ok(()) 23 | } 24 | 25 | #[tokio::test] 26 | async fn test_keep_alive() -> Result<()> { 27 | let session = env::var("TARKOV_SESSION").unwrap(); 28 | let t = Tarkov::from_session(&session); 29 | 30 | t.keep_alive().await?; 31 | 32 | Ok(()) 33 | } 34 | 35 | #[tokio::test] 36 | async fn test_constants() -> Result<()> { 37 | let session = env::var("TARKOV_SESSION").unwrap(); 38 | let t = Tarkov::from_session(&session); 39 | 40 | let _ = t.get_items().await?; 41 | let _ = t.get_item_prices().await?; 42 | let _ = t.get_locations().await?; 43 | let _ = t.get_weather().await?; 44 | let _ = t.get_i18n("en").await?; 45 | 46 | Ok(()) 47 | } 48 | 49 | #[tokio::test] 50 | async fn test_flea_market_getters() -> Result<()> { 51 | let session = env::var("TARKOV_SESSION").unwrap(); 52 | let t = Tarkov::from_session(&session); 53 | 54 | let _ = t.search_market(0, 15, MarketFilter::default()).await?; 55 | 56 | // USD price 57 | let _ = t.get_item_price("5696686a4bdc2da3298b456a").await?; 58 | 59 | Ok(()) 60 | } 61 | 62 | #[tokio::test] 63 | async fn test_flea_market_buying() -> Result<()> { 64 | let session = env::var("TARKOV_SESSION").unwrap(); 65 | let t = Tarkov::from_session(&session); 66 | 67 | let profiles = t.get_profiles().await?; 68 | let profile = profiles 69 | .into_iter() 70 | .find(|p| p.info.side != Side::Savage) 71 | .unwrap(); 72 | 73 | let offers = t 74 | .search_market( 75 | 0, 76 | 15, 77 | MarketFilter { 78 | max_price: Some(2000), 79 | handbook_id: Some("5b47574386f77428ca22b33e"), 80 | owner_type: Owner::Player, 81 | hide_bartering_offers: true, 82 | currency: Currency::Rouble, 83 | ..MarketFilter::default() 84 | }, 85 | ) 86 | .await?; 87 | 88 | let current_time = SystemTime::now(); 89 | let epoch_time = current_time.duration_since(UNIX_EPOCH).unwrap().as_secs(); 90 | let offer = offers 91 | .offers 92 | .into_iter() 93 | .find(|o| o.start_time + 60 <= epoch_time && o.end_time >= epoch_time) 94 | .unwrap(); 95 | 96 | let rouble = &profile 97 | .inventory 98 | .items 99 | .into_iter() 100 | .find(|i| i.schema_id == "5449016a4bdc2d6f028b456f") 101 | .unwrap(); 102 | 103 | let _ = t 104 | .buy_item( 105 | &offer.id, 106 | 1, 107 | &[BarterItem { 108 | id: rouble.id.to_owned(), 109 | count: offer.requirements_cost as f64, 110 | }], 111 | ) 112 | .await?; 113 | 114 | Ok(()) 115 | } 116 | 117 | #[tokio::test] 118 | async fn test_flea_market_selling() -> Result<()> { 119 | let session = env::var("TARKOV_SESSION").unwrap(); 120 | let t = Tarkov::from_session(&session); 121 | 122 | let profiles = t.get_profiles().await?; 123 | let profile = profiles 124 | .into_iter() 125 | .find(|p| p.info.side != Side::Savage) 126 | .unwrap(); 127 | 128 | let painkiller = &profile 129 | .inventory 130 | .items 131 | .into_iter() 132 | .find(|i| i.schema_id == "544fb37f4bdc2dee738b4567") 133 | .unwrap(); 134 | 135 | let _ = t 136 | .offer_item( 137 | &[&painkiller.id], 138 | &[Requirement { 139 | schema_id: "5449016a4bdc2d6f028b456f".to_string(), 140 | count: 2000.0, 141 | }], 142 | false, 143 | ) 144 | .await?; 145 | 146 | Ok(()) 147 | } 148 | 149 | #[tokio::test] 150 | async fn test_trader_getters() -> Result<()> { 151 | let session = env::var("TARKOV_SESSION").unwrap(); 152 | let t = Tarkov::from_session(&session); 153 | 154 | let traders = t.get_traders().await?; 155 | let trader = traders.get(0).unwrap(); 156 | let trader = t.get_trader(&trader.id).await?; 157 | 158 | let _ = t.get_trader_items(&trader.id).await?; 159 | 160 | Ok(()) 161 | } 162 | 163 | #[tokio::test] 164 | async fn test_trader_buying() -> Result<()> { 165 | let session = env::var("TARKOV_SESSION").unwrap(); 166 | let t = Tarkov::from_session(&session); 167 | 168 | let profiles = t.get_profiles().await?; 169 | let profile = profiles 170 | .into_iter() 171 | .find(|p| p.info.side != Side::Savage) 172 | .unwrap(); 173 | 174 | let traders = t.get_traders().await?; 175 | let trader = traders 176 | .into_iter() 177 | .find(|t| t.nickname == "Терапевт") 178 | .unwrap(); 179 | 180 | let rouble = &profile 181 | .inventory 182 | .items 183 | .into_iter() 184 | .find(|i| { 185 | i.schema_id == "5449016a4bdc2d6f028b456f" 186 | && i.upd.as_ref().unwrap().stack_objects_count >= Some(3990) 187 | }) 188 | .unwrap(); 189 | 190 | let _ = t 191 | .trade_item( 192 | &trader.id, 193 | "5e064f5deb009468d90baef7", // ID might change. 194 | 1, 195 | &[BarterItem { 196 | id: rouble.id.to_owned(), 197 | count: 3990.0, // Assume price is 3990₽. 198 | }], 199 | ) 200 | .await?; 201 | 202 | Ok(()) 203 | } 204 | 205 | #[tokio::test] 206 | async fn test_trader_selling() -> Result<()> { 207 | let session = env::var("TARKOV_SESSION").unwrap(); 208 | let t = Tarkov::from_session(&session); 209 | 210 | let profiles = t.get_profiles().await?; 211 | let profile = profiles 212 | .into_iter() 213 | .find(|p| p.info.side != Side::Savage) 214 | .unwrap(); 215 | 216 | let traders = t.get_traders().await?; 217 | let trader = traders 218 | .into_iter() 219 | .find(|t| t.nickname == "Терапевт") 220 | .unwrap(); 221 | 222 | let painkiller = &profile 223 | .inventory 224 | .items 225 | .into_iter() 226 | .find(|i| i.schema_id == "544fb37f4bdc2dee738b4567") 227 | .unwrap(); 228 | 229 | let _ = t.sell_item(&trader.id, &painkiller.id, 1).await?; 230 | 231 | Ok(()) 232 | } 233 | -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | handle_error, handle_error2, Error, ErrorResponse, Result, Tarkov, GAME_VERSION, 3 | LAUNCHER_ENDPOINT, LAUNCHER_VERSION, PROD_ENDPOINT, 4 | }; 5 | use flate2::read::ZlibDecoder; 6 | use hyper::body::Buf; 7 | use hyper::client::connect::dns::GaiResolver; 8 | use hyper::client::{Client, HttpConnector}; 9 | use hyper::Request; 10 | use hyper::StatusCode; 11 | use hyper::{Body, Method}; 12 | use hyper_tls::HttpsConnector; 13 | use log::debug; 14 | use serde::de::DeserializeOwned; 15 | use serde::{Deserialize, Serialize}; 16 | use std::io::Read; 17 | 18 | #[derive(Debug, Serialize)] 19 | #[serde(rename_all = "camelCase")] 20 | struct LoginRequest<'a> { 21 | email: &'a str, 22 | pass: &'a str, 23 | hw_code: &'a str, 24 | captcha: Option<&'a str>, 25 | } 26 | 27 | #[derive(Debug, Deserialize)] 28 | pub(crate) struct Auth { 29 | pub aid: String, 30 | pub lang: String, 31 | pub region: Option, 32 | #[serde(rename = "gameVersion")] 33 | pub game_version: Option, 34 | #[serde(rename = "dataCenters")] 35 | pub data_centers: Vec, 36 | #[serde(rename = "ipRegion")] 37 | pub ip_region: String, 38 | pub token_type: String, 39 | pub expires_in: u64, 40 | pub access_token: String, 41 | pub refresh_token: String, 42 | } 43 | 44 | #[derive(Debug, Deserialize)] 45 | struct LoginResponse { 46 | #[serde(flatten)] 47 | error: ErrorResponse, 48 | #[serde(default)] 49 | data: serde_json::Value, 50 | } 51 | 52 | /// Login error 53 | #[derive(Debug, err_derive::Error)] 54 | pub enum LoginError { 55 | /// Bad login, invalid email or password. 56 | #[error(display = "bad login, wrong email or password.")] 57 | BadLogin, 58 | /// 2FA code is required to continue authentication. 59 | #[error(display = "2fa is required")] 60 | TwoFactorRequired, 61 | /// Captcha response is required to continue authentication. 62 | #[error(display = "captcha is required")] 63 | CaptchaRequired, 64 | /// Incorrect 2FA code. 65 | #[error(display = "incorrect 2FA code")] 66 | BadTwoFactorCode, 67 | /// Rate limited after too many bad login attempts. 68 | #[error(display = "too many login attempts")] 69 | RateLimited, 70 | /// Library contains the wrong major version. 71 | #[error(display = "wrong major version")] 72 | WrongMajorVersion, 73 | } 74 | 75 | pub(crate) async fn login( 76 | client: &Client>, Body>, 77 | email: &str, 78 | password: &str, 79 | captcha: Option<&str>, 80 | hwid: &str, 81 | ) -> Result { 82 | let url = format!( 83 | "{}/launcher/login?launcherVersion={}&branch=live", 84 | LAUNCHER_ENDPOINT, LAUNCHER_VERSION 85 | ); 86 | let password = format!("{:x}", md5::compute(&password)); 87 | 88 | let body = LoginRequest { 89 | email, 90 | pass: &password, 91 | hw_code: hwid, 92 | captcha, 93 | }; 94 | 95 | let res: LoginResponse = post_json(client, &url, &body).await?; 96 | handle_error2(res.error)?; 97 | 98 | Ok(Deserialize::deserialize(res.data)?) 99 | } 100 | 101 | #[derive(Debug, Serialize)] 102 | #[serde(rename_all = "camelCase")] 103 | struct SecurityLoginRequest<'a> { 104 | email: &'a str, 105 | hw_code: &'a str, 106 | activate_code: &'a str, 107 | } 108 | 109 | #[derive(Debug, Deserialize)] 110 | struct SecurityLoginResponse { 111 | #[serde(flatten)] 112 | error: ErrorResponse, 113 | } 114 | 115 | pub(crate) async fn activate_hardware( 116 | client: &Client>, Body>, 117 | email: &str, 118 | code: &str, 119 | hwid: &str, 120 | ) -> Result<()> { 121 | let url = format!( 122 | "{}/launcher/hardwareCode/activate?launcherVersion={}", 123 | LAUNCHER_ENDPOINT, LAUNCHER_VERSION 124 | ); 125 | 126 | let body = SecurityLoginRequest { 127 | email, 128 | hw_code: hwid, 129 | activate_code: code, 130 | }; 131 | 132 | let res: SecurityLoginResponse = post_json(client, &url, &body).await?; 133 | handle_error2(res.error) 134 | } 135 | 136 | #[derive(Debug, Serialize)] 137 | #[serde(rename_all = "camelCase")] 138 | struct ExchangeRequest<'a> { 139 | version: ExchangeVersion<'a>, 140 | hw_code: &'a str, 141 | } 142 | 143 | #[derive(Debug, Serialize)] 144 | #[serde(rename_all = "camelCase")] 145 | struct ExchangeVersion<'a> { 146 | major: &'a str, 147 | game: &'a str, 148 | backend: &'a str, 149 | } 150 | 151 | #[derive(Debug, Deserialize)] 152 | struct ExchangeResponse { 153 | #[serde(flatten)] 154 | error: ErrorResponse, 155 | data: Option, 156 | } 157 | 158 | /// Authenticated user session. 159 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 160 | pub struct Session { 161 | queued: bool, 162 | /// Session cookie. 163 | pub session: String, 164 | } 165 | 166 | pub(crate) async fn exchange_access_token( 167 | client: &Client>, Body>, 168 | access_token: &str, 169 | hwid: &str, 170 | ) -> Result { 171 | let url = format!( 172 | "{}/launcher/game/start?launcherVersion={}&branch=live", 173 | PROD_ENDPOINT, LAUNCHER_VERSION 174 | ); 175 | 176 | let body = ExchangeRequest { 177 | version: ExchangeVersion { 178 | major: GAME_VERSION, 179 | game: "live", 180 | backend: "6", 181 | }, 182 | hw_code: hwid, 183 | }; 184 | 185 | debug!("Sending request to {} ({:?})", url, body); 186 | let req = Request::builder() 187 | .uri(url) 188 | .method(Method::POST) 189 | .header("Content-Type", "application/json") 190 | .header("User-Agent", format!("BSG Launcher {}", LAUNCHER_VERSION)) 191 | .header("Authorization", access_token) 192 | .body(Body::from(serde_json::to_string(&body)?))?; 193 | let res = client.request(req).await?; 194 | 195 | match res.status() { 196 | StatusCode::OK => { 197 | let body = hyper::body::to_bytes(res.into_body()).await?; 198 | let mut decode = ZlibDecoder::new(body.bytes()); 199 | let mut body = String::new(); 200 | decode.read_to_string(&mut body)?; 201 | debug!("Response: {}", body); 202 | 203 | let res = serde_json::from_slice::(body.as_bytes())?; 204 | handle_error(res.error, res.data) 205 | } 206 | _ => Err(Error::Status(res.status())), 207 | } 208 | } 209 | 210 | async fn post_json( 211 | client: &Client>, Body>, 212 | url: &str, 213 | body: &S, 214 | ) -> Result { 215 | debug!("Sending request to {} ({:?})", url, body); 216 | let req = Request::builder() 217 | .uri(url) 218 | .method(Method::POST) 219 | .header("Content-Type", "application/json") 220 | .header("User-Agent", format!("BSG Launcher {}", LAUNCHER_VERSION)) 221 | .body(Body::from(serde_json::to_string(&body)?))?; 222 | let res = client.request(req).await?; 223 | 224 | match res.status() { 225 | StatusCode::OK => { 226 | let body = hyper::body::to_bytes(res.into_body()).await?; 227 | let mut decode = ZlibDecoder::new(body.bytes()); 228 | let mut body = String::new(); 229 | decode.read_to_string(&mut body)?; 230 | debug!("Response: {}", body); 231 | 232 | Ok(serde_json::from_slice::(body.as_bytes())?) 233 | } 234 | _ => Err(Error::Status(res.status())), 235 | } 236 | } 237 | 238 | impl Tarkov { 239 | /// Keep the current session alive. Session expires after 30 seconds of idling. 240 | pub async fn keep_alive(&self) -> Result<()> { 241 | let url = format!("{}/client/game/keepalive", PROD_ENDPOINT); 242 | let res: ErrorResponse = self.post_json(&url, &{}).await?; 243 | 244 | match res.code { 245 | 0 => Ok(()), 246 | _ => Err(Error::UnknownAPIError(res.code)), 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An unofficial client library for the [Escape from Tarkov](https://escapefromtarkov.com) (EFT) API. 2 | //! 3 | //! To get started, login to EFT with `Tarkov::login`, `from_access_token`, or `from_session`. 4 | //! Additionally, on a new session, a profile must be selected with `select_profile` before continuing. 5 | //! 6 | //! Once authenticated, the resulting value can be used to make further API requests. 7 | //! 8 | //! See [Tarkov](struct.Tarkov.html) for a list of available methods. 9 | //! 10 | //! For examples, see the `examples` directory in the source tree. 11 | 12 | #![warn(missing_docs)] 13 | 14 | use crate::auth::LoginError; 15 | use crate::hwid::generate_hwid; 16 | use crate::profile::ProfileError; 17 | use crate::ragfair::RagfairError; 18 | use crate::trading::TradingError; 19 | use err_derive::Error; 20 | use flate2::read::ZlibDecoder; 21 | use hyper::body::Buf; 22 | use hyper::client::connect::dns::GaiResolver; 23 | use hyper::client::{Client, HttpConnector}; 24 | use hyper::Body; 25 | use hyper::Request; 26 | use hyper::{Method, StatusCode}; 27 | use hyper_tls::HttpsConnector; 28 | use log::debug; 29 | use serde::de::DeserializeOwned; 30 | use serde::{Deserialize, Serialize}; 31 | use std::io::Read; 32 | 33 | const GAME_VERSION: &str = "0.12.7.9018"; 34 | const LAUNCHER_VERSION: &str = "10.2.0.1149"; 35 | const UNITY_VERSION: &str = "2018.4.13f1"; 36 | 37 | const LAUNCHER_ENDPOINT: &str = "https://launcher.escapefromtarkov.com"; 38 | const PROD_ENDPOINT: &str = "https://prod.escapefromtarkov.com"; 39 | const TRADING_ENDPOINT: &str = "https://trading.escapefromtarkov.com"; 40 | const RAGFAIR_ENDPOINT: &str = "https://ragfair.escapefromtarkov.com"; 41 | 42 | mod bad_json; 43 | 44 | /// Structs for authentication. 45 | pub mod auth; 46 | /// Structs for game constants API. 47 | pub mod constant; 48 | /// Structs for the Friend API. 49 | pub mod friend; 50 | /// Helper functions for hardware ID. 51 | pub mod hwid; 52 | /// Structs for inventory and items. 53 | pub mod inventory; 54 | /// Flea market search helpers. 55 | pub mod market_filter; 56 | /// Structs for the Profile API. 57 | pub mod profile; 58 | /// Structs for the Flea Market (Ragfair) API. 59 | pub mod ragfair; 60 | /// Structs for the Trading API. 61 | pub mod trading; 62 | 63 | /// Common error enum returned by most functions. 64 | #[derive(Debug, Error)] 65 | pub enum Error { 66 | /// A `std::io` error 67 | #[error(display = "io error: {}", _0)] 68 | Io(#[error(source)] std::io::Error), 69 | /// HTTP request error. 70 | #[error(display = "http error: {}", _0)] 71 | Http(#[error(source)] http::Error), 72 | /// A `hyper` crate error. 73 | #[error(display = "hyper error: {}", _0)] 74 | Hyper(#[error(source)] hyper::Error), 75 | /// A `serde_json` error. 76 | #[error(display = "json error: {}", _0)] 77 | Json(#[error(source)] serde_json::error::Error), 78 | /// Generic non-success response from the API. 79 | #[error(display = "non-success response from api: {}", _0)] 80 | Status(StatusCode), 81 | /// Invalid or missing parameters. 82 | #[error(display = "invalid or missing login parameters")] 83 | InvalidParameters, 84 | 85 | /// Unidentified error within the EFT API. 86 | #[error(display = "unidentified login error with error code: {}", _0)] 87 | UnknownAPIError(u64), 88 | /// Not authorized to API or profile is not selected. 89 | #[error(display = "not authorized or game profile not selected")] 90 | NotAuthorized, 91 | /// EFT API is down for maintenance. 92 | #[error(display = "api is down for maintenance")] 93 | Maintenance, 94 | /// Backend error. No other information is given. 95 | #[error(display = "backend error")] 96 | BackendError, 97 | /// Authentication API error. 98 | #[error(display = "login api error: {}", _0)] 99 | LoginError(#[error(source)] LoginError), 100 | /// Profile API error. 101 | #[error(display = "profile api error: {}", _0)] 102 | ProfileError(#[error(source)] ProfileError), 103 | /// Trading API error. 104 | #[error(display = "trading api error: {}", _0)] 105 | TradingError(#[error(source)] TradingError), 106 | /// Ragfair API error. 107 | #[error(display = "trading api error: {}", _0)] 108 | RagfairError(#[error(source)] RagfairError), 109 | } 110 | 111 | /// `Result` alias type. 112 | pub type Result = std::result::Result; 113 | 114 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 115 | struct ErrorResponse { 116 | #[serde(rename = "err")] 117 | code: u64, 118 | #[serde(rename = "errmsg")] 119 | message: Option, 120 | } 121 | 122 | /// Client for the EFT API. 123 | pub struct Tarkov { 124 | client: Client>, Body>, 125 | /// Hardware ID 126 | pub hwid: String, 127 | /// Session cookie 128 | pub session: String, 129 | } 130 | 131 | impl Tarkov { 132 | /// Login with email and password. 133 | pub async fn login(email: &str, password: &str, hwid: &str) -> Result { 134 | if email.is_empty() || password.is_empty() || hwid.is_empty() { 135 | return Err(Error::InvalidParameters); 136 | } 137 | 138 | let https = HttpsConnector::new(); 139 | let client = Client::builder().build::<_, Body>(https); 140 | 141 | let user = auth::login(&client, email, password, None, &hwid).await?; 142 | let session = auth::exchange_access_token(&client, &user.access_token, &hwid).await?; 143 | 144 | Ok(Tarkov { 145 | client, 146 | hwid: hwid.to_string(), 147 | session: session.session, 148 | }) 149 | } 150 | 151 | /// Login with email, password and captcha. 152 | pub async fn login_with_captcha( 153 | email: &str, 154 | password: &str, 155 | captcha: &str, 156 | hwid: &str, 157 | ) -> Result { 158 | if email.is_empty() || password.is_empty() || captcha.is_empty() || hwid.is_empty() { 159 | return Err(Error::InvalidParameters); 160 | } 161 | 162 | let https = HttpsConnector::new(); 163 | let client = Client::builder().build::<_, Body>(https); 164 | 165 | let user = auth::login(&client, email, password, Some(captcha), &hwid).await?; 166 | let session = auth::exchange_access_token(&client, &user.access_token, &hwid).await?; 167 | 168 | Ok(Tarkov { 169 | client, 170 | hwid: hwid.to_string(), 171 | session: session.session, 172 | }) 173 | } 174 | 175 | /// Login with email, password and 2FA code. 176 | pub async fn login_with_2fa( 177 | email: &str, 178 | password: &str, 179 | code: &str, 180 | hwid: &str, 181 | ) -> Result { 182 | if email.is_empty() || password.is_empty() || code.is_empty() || hwid.is_empty() { 183 | return Err(Error::InvalidParameters); 184 | } 185 | 186 | let https = HttpsConnector::new(); 187 | let client = Client::builder().build::<_, Body>(https); 188 | 189 | let _ = auth::activate_hardware(&client, email, code, &hwid).await?; 190 | let user = auth::login(&client, email, password, None, &hwid).await?; 191 | let session = auth::exchange_access_token(&client, &user.access_token, &hwid).await?; 192 | 193 | Ok(Tarkov { 194 | client, 195 | hwid: hwid.to_string(), 196 | session: session.session, 197 | }) 198 | } 199 | 200 | /// Login with a Bearer token. 201 | pub async fn from_access_token(access_token: &str, hwid: &str) -> Result { 202 | if access_token.is_empty() || hwid.is_empty() { 203 | return Err(Error::InvalidParameters); 204 | } 205 | 206 | let https = HttpsConnector::new(); 207 | let client = Client::builder().build::<_, Body>(https); 208 | let session = auth::exchange_access_token(&client, &access_token, &hwid).await?; 209 | 210 | Ok(Tarkov { 211 | client, 212 | hwid: hwid.to_string(), 213 | session: session.session, 214 | }) 215 | } 216 | 217 | /// Login with a cookie session (AKA `PHPSESSID`). 218 | pub fn from_session(session: &str) -> Self { 219 | let https = HttpsConnector::new(); 220 | let client = Client::builder().build::<_, Body>(https); 221 | 222 | Tarkov { 223 | client, 224 | hwid: generate_hwid(), 225 | session: session.to_string(), 226 | } 227 | } 228 | 229 | async fn post_json(&self, url: &str, body: &S) -> Result 230 | where 231 | S: serde::Serialize + ?Sized + std::fmt::Debug, 232 | T: DeserializeOwned, 233 | { 234 | debug!("Sending request to {} ({:?})", url, body); 235 | let body = match serde_json::to_string(&body) { 236 | Ok(body) => Ok(Body::from(if body == "null" { 237 | "{}".to_string() 238 | } else { 239 | body 240 | })), 241 | Err(e) => Err(e), 242 | }?; 243 | 244 | let req = Request::builder() 245 | .uri(url) 246 | .method(Method::POST) 247 | .header("Content-Type", "application/json") 248 | .header( 249 | "User-Agent", 250 | format!( 251 | "UnityPlayer/{} (UnityWebRequest/1.0, libcurl/7.52.0-DEV)", 252 | UNITY_VERSION 253 | ), 254 | ) 255 | .header("App-Version", format!("EFT Client {}", GAME_VERSION)) 256 | .header("X-Unity-Version", UNITY_VERSION) 257 | .header("Cookie", format!("PHPSESSID={}", self.session)) 258 | .body(body)?; 259 | let res = self.client.request(req).await?; 260 | 261 | match res.status() { 262 | StatusCode::OK => { 263 | let body = hyper::body::to_bytes(res.into_body()).await?; 264 | let mut decode = ZlibDecoder::new(body.bytes()); 265 | let mut body = String::new(); 266 | decode.read_to_string(&mut body)?; 267 | debug!("Response: {}", body); 268 | 269 | Ok(serde_json::from_slice::(body.as_bytes())?) 270 | } 271 | _ => Err(Error::Status(res.status())), 272 | } 273 | } 274 | } 275 | 276 | pub(crate) fn handle_error(error: ErrorResponse, ret: Option) -> Result { 277 | handle_error2(error)?; 278 | Ok(ret.expect("API returned no errors but `data` is unavailable.")) 279 | } 280 | 281 | pub(crate) fn handle_error2(error: ErrorResponse) -> Result<()> { 282 | match error.code { 283 | 0 => Ok(()), 284 | 201 => Err(Error::NotAuthorized)?, 285 | 205 => Err(ProfileError::InvalidUserID)?, 286 | 206 => Err(LoginError::BadLogin)?, 287 | 207 => Err(Error::InvalidParameters)?, 288 | 209 => Err(LoginError::TwoFactorRequired)?, 289 | 211 => Err(LoginError::BadTwoFactorCode)?, 290 | 214 => Err(LoginError::CaptchaRequired)?, 291 | 228 => Err(RagfairError::InvalidBarterItems)?, 292 | 230 => Err(LoginError::RateLimited)?, 293 | 232 => Err(LoginError::WrongMajorVersion)?, 294 | 263 => Err(Error::Maintenance)?, 295 | 1000 => Err(Error::BackendError)?, 296 | 1501 => Err(RagfairError::MaxOfferCount)?, 297 | 1502 => Err(RagfairError::InsufficientTaxFunds)?, 298 | 1507 => Err(RagfairError::OfferNotFound)?, 299 | 1510 => Err(TradingError::BadLoyaltyLevel)?, 300 | 1512 => Err(RagfairError::OfferNotAvailableYet)?, 301 | 1514 => Err(TradingError::TransactionError)?, 302 | _ => Err(Error::UnknownAPIError(error.code)), 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/ragfair.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | handle_error, handle_error2, Error, ErrorResponse, Result, Tarkov, PROD_ENDPOINT, 3 | RAGFAIR_ENDPOINT, 4 | }; 5 | 6 | use crate::inventory::{BarterItem, InventoryUpdate, Item, MoveItemRequest, RagfairResponseData}; 7 | use crate::market_filter::{Currency, MarketFilter, Owner, SortBy, SortDirection}; 8 | use serde::{Deserialize, Serialize}; 9 | use std::collections::HashMap; 10 | 11 | /// Ragfair error 12 | #[derive(Debug, err_derive::Error)] 13 | pub enum RagfairError { 14 | /// Offer is not available yet. 15 | #[error(display = "offer not available yet")] 16 | OfferNotAvailableYet, 17 | /// Offer not found, sold out or out of stock. 18 | #[error(display = "offer not found, sold out or out of stock")] 19 | OfferNotFound, 20 | /// Provided `BarterItem` is invalid, not enough quantities available or not found. 21 | #[error(display = "barter items provided cannot be found")] 22 | InvalidBarterItems, 23 | /// Maximum outstanding offer count of 3 was reached. 24 | #[error(display = "maximum offer count of 3 was reached")] 25 | MaxOfferCount, 26 | /// Insufficient funds to pay the flea market fee. 27 | #[error(display = "insufficient funds to pay market fee")] 28 | InsufficientTaxFunds, 29 | } 30 | 31 | #[derive(Debug, Serialize)] 32 | #[serde(rename_all = "camelCase")] 33 | struct SearchRequest<'a> { 34 | page: u64, 35 | limit: u64, 36 | sort_type: SortBy, 37 | sort_direction: SortDirection, 38 | currency: Currency, 39 | price_from: u64, 40 | price_to: u64, 41 | quantity_from: u64, 42 | quantity_to: u64, 43 | condition_from: u64, 44 | condition_to: u64, 45 | one_hour_expiration: bool, 46 | remove_bartering: bool, 47 | offer_owner_type: Owner, 48 | only_functional: bool, 49 | update_offer_count: bool, 50 | handbook_id: &'a str, 51 | linked_search_id: &'a str, 52 | needed_search_id: &'a str, 53 | //build_items: {} 54 | //build_count: u64, 55 | tm: u64, 56 | } 57 | 58 | #[derive(Debug, Deserialize)] 59 | #[serde(rename_all = "camelCase")] 60 | struct SearchResponse { 61 | #[serde(flatten)] 62 | error: ErrorResponse, 63 | data: Option, 64 | } 65 | 66 | /// Market search result 67 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 68 | #[serde(rename_all = "camelCase")] 69 | pub struct SearchResult { 70 | /// Market categories 71 | pub categories: HashMap, 72 | /// Available offers 73 | pub offers: Vec, 74 | /// Number of items for sale 75 | pub offers_count: u64, 76 | /// Selected category ID 77 | pub selected_category: String, 78 | } 79 | 80 | /// Market offer 81 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 82 | #[serde(rename_all = "camelCase")] 83 | pub struct Offer { 84 | /// Offer ID 85 | #[serde(rename = "_id")] 86 | pub id: String, 87 | /// ? 88 | pub int_id: u64, 89 | /// Merchant profile 90 | pub user: User, 91 | /// ? 92 | pub root: String, 93 | /// Items for sale 94 | pub items: Vec, 95 | /// Items cost 96 | pub items_cost: u64, 97 | /// Items wanted in return 98 | pub requirements: Vec, 99 | /// Requirement items cost 100 | pub requirements_cost: u64, 101 | /// Summary cost 102 | pub summary_cost: u64, 103 | /// ? 104 | pub sell_in_one_piece: bool, 105 | /// Time when item was listed on the market. 106 | /// 107 | /// Add 60 seconds for the true start time, when the item will be available for purchase. 108 | pub start_time: u64, 109 | /// Offer expiry time 110 | pub end_time: u64, 111 | /// Merchant loyalty level 112 | pub loyalty_level: u64, 113 | } 114 | 115 | /// Merchant profile 116 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 117 | #[serde(rename_all = "camelCase")] 118 | pub struct User { 119 | /// Merchant ID 120 | pub id: String, 121 | /// Merchant member type 122 | pub member_type: u64, 123 | /// Merchant nickname 124 | pub nickname: Option, 125 | /// Merchant rating 126 | pub rating: Option, 127 | /// Merchant rating is growing 128 | pub is_rating_growing: Option, 129 | /// Merchant avatar 130 | pub avatar: Option, 131 | } 132 | 133 | /// Offer requirement 134 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 135 | #[serde(rename_all = "camelCase")] 136 | pub struct Requirement { 137 | /// Item localization schema ID 138 | #[serde(rename = "_tpl")] 139 | pub schema_id: String, 140 | /// Item count 141 | pub count: f64, 142 | } 143 | 144 | #[derive(Debug, Serialize)] 145 | struct BuyItemRequest<'a> { 146 | #[serde(rename = "Action")] 147 | action: &'a str, 148 | offers: &'a [BuyOffer<'a>], 149 | } 150 | 151 | #[derive(Debug, Serialize)] 152 | struct BuyOffer<'a> { 153 | id: &'a str, 154 | count: u64, 155 | items: &'a [BarterItem], 156 | } 157 | 158 | #[derive(Debug, Serialize)] 159 | struct BuyItem { 160 | item: String, 161 | count: f64, 162 | } 163 | 164 | #[derive(Debug, Deserialize)] 165 | #[serde(rename_all = "camelCase")] 166 | struct BuyItemResponse { 167 | #[serde(flatten)] 168 | error: ErrorResponse, 169 | data: serde_json::Value, 170 | } 171 | 172 | #[derive(Debug, Serialize)] 173 | #[serde(rename_all = "camelCase")] 174 | struct GetPriceRequest<'a> { 175 | template_id: &'a str, 176 | } 177 | 178 | #[derive(Debug, Deserialize)] 179 | struct GetPriceResponse { 180 | #[serde(flatten)] 181 | error: ErrorResponse, 182 | data: Option, 183 | } 184 | 185 | /// Ragfair item price 186 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 187 | #[serde(rename_all = "camelCase")] 188 | pub struct Price { 189 | /// Item localization schema ID 190 | #[serde(rename = "templateId")] 191 | pub schema_id: String, 192 | /// Minimum item price 193 | pub min: f64, 194 | /// Maximum item price 195 | pub max: f64, 196 | /// Average item price 197 | pub avg: f64, 198 | } 199 | 200 | #[derive(Debug, Serialize)] 201 | #[serde(rename_all = "camelCase")] 202 | struct SellItemRequest<'a> { 203 | #[serde(rename = "Action")] 204 | action: &'a str, 205 | sell_in_one_piece: bool, 206 | items: &'a [&'a str], 207 | requirements: &'a [SellRequirement], 208 | } 209 | 210 | #[derive(Debug, Serialize)] 211 | #[serde(rename_all = "camelCase")] 212 | struct SellRequirement { 213 | #[serde(flatten)] 214 | requirement: Requirement, 215 | level: u64, 216 | side: u8, 217 | only_functional: bool, 218 | } 219 | 220 | #[derive(Debug, Deserialize)] 221 | #[serde(rename_all = "camelCase")] 222 | struct SellItemResponse { 223 | #[serde(flatten)] 224 | error: ErrorResponse, 225 | data: serde_json::Value, 226 | } 227 | 228 | impl Tarkov { 229 | /// Search the flea market. 230 | pub async fn search_market<'a>( 231 | &self, 232 | page: u64, 233 | limit: u64, 234 | filter: MarketFilter<'_>, 235 | ) -> Result { 236 | if limit == 0 { 237 | return Err(Error::InvalidParameters); 238 | } 239 | 240 | let body = SearchRequest { 241 | page, 242 | limit, 243 | sort_type: filter.sort_type, 244 | sort_direction: filter.sort_direction, 245 | currency: filter.currency, 246 | price_from: filter.min_price.unwrap_or(0), 247 | price_to: filter.max_price.unwrap_or(0), 248 | quantity_from: filter.min_quantity.unwrap_or(0), 249 | quantity_to: filter.max_quantity.unwrap_or(0), 250 | condition_from: filter.min_condition.unwrap_or(0), 251 | condition_to: filter.max_condition.unwrap_or(0), 252 | one_hour_expiration: filter.expiring_within_hour, 253 | remove_bartering: filter.hide_bartering_offers, 254 | offer_owner_type: filter.owner_type, 255 | only_functional: filter.hide_inoperable_weapons, 256 | update_offer_count: true, 257 | handbook_id: &filter.handbook_id.unwrap_or(""), 258 | linked_search_id: &filter.linked_search_id.unwrap_or(""), 259 | needed_search_id: &filter.required_search_id.unwrap_or(""), 260 | tm: 1, 261 | }; 262 | 263 | let url = format!("{}/client/ragfair/find", RAGFAIR_ENDPOINT); 264 | let res: SearchResponse = self.post_json(&url, &body).await?; 265 | 266 | handle_error(res.error, res.data) 267 | } 268 | 269 | /// Get the item price data from the flea market. 270 | pub async fn get_item_price(&self, schema_id: &str) -> Result { 271 | if schema_id.is_empty() { 272 | return Err(Error::InvalidParameters); 273 | } 274 | 275 | let url = format!("{}/client/ragfair/itemMarketPrice", RAGFAIR_ENDPOINT); 276 | let body = GetPriceRequest { 277 | template_id: schema_id, 278 | }; 279 | 280 | let res: GetPriceResponse = self.post_json(&url, &body).await?; 281 | handle_error(res.error, res.data) 282 | } 283 | 284 | /// Buy items from the flea market. 285 | pub async fn buy_item( 286 | &self, 287 | offer_id: &str, 288 | quantity: u64, 289 | barter_items: &[BarterItem], 290 | ) -> Result { 291 | if offer_id.is_empty() || quantity == 0 || barter_items.is_empty() { 292 | return Err(Error::InvalidParameters); 293 | } 294 | 295 | let url = format!("{}/client/game/profile/items/moving", PROD_ENDPOINT); 296 | let body = &MoveItemRequest { 297 | data: &[BuyItemRequest { 298 | action: "RagFairBuyOffer", 299 | offers: &[BuyOffer { 300 | id: offer_id, 301 | count: quantity, 302 | items: barter_items, 303 | }], 304 | }], 305 | tm: 2, 306 | }; 307 | 308 | let res: BuyItemResponse = self.post_json(&url, body).await?; 309 | handle_error2(res.error)?; 310 | 311 | let res: RagfairResponseData = Deserialize::deserialize(res.data)?; 312 | if !res.errors.is_empty() { 313 | let error = &res.errors[0]; 314 | match error.code { 315 | 1503 | 1506 | 1507 => return Err(RagfairError::OfferNotFound)?, 316 | _ => return Err(Error::UnknownAPIError(error.code)), 317 | } 318 | } 319 | 320 | let items: InventoryUpdate = Deserialize::deserialize(res.items)?; 321 | Ok(items) 322 | } 323 | 324 | /// List an item for sale on the flea market. 325 | pub async fn offer_item( 326 | &self, 327 | items: &[&str], 328 | requirements: &[Requirement], 329 | sell_all: bool, 330 | ) -> Result { 331 | if items.is_empty() || requirements.is_empty() { 332 | return Err(Error::InvalidParameters); 333 | } 334 | 335 | let url = format!("{}/client/game/profile/items/moving", PROD_ENDPOINT); 336 | let body = &MoveItemRequest { 337 | data: &[SellItemRequest { 338 | action: "RagFairAddOffer", 339 | sell_in_one_piece: sell_all, 340 | items, 341 | requirements: &requirements 342 | .into_iter() 343 | .map(|r| SellRequirement { 344 | requirement: r.to_owned(), 345 | level: 0, 346 | side: 0, 347 | only_functional: false, 348 | }) 349 | .collect::>(), 350 | }], 351 | tm: 2, 352 | }; 353 | 354 | let res: SellItemResponse = self.post_json(&url, body).await?; 355 | handle_error2(res.error)?; 356 | 357 | let res: RagfairResponseData = Deserialize::deserialize(res.data)?; 358 | if !res.errors.is_empty() { 359 | let error = &res.errors[0]; 360 | return Err(Error::UnknownAPIError(error.code)); 361 | } 362 | 363 | let items: InventoryUpdate = Deserialize::deserialize(res.items)?; 364 | Ok(items) 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/trading.rs: -------------------------------------------------------------------------------- 1 | use crate::inventory::{ 2 | BarterItem, InventoryUpdate, Item, MoveItemRequest, RagfairResponseData, Upd, 3 | }; 4 | use crate::{ 5 | handle_error, handle_error2, Error, ErrorResponse, Result, Tarkov, PROD_ENDPOINT, 6 | TRADING_ENDPOINT, 7 | }; 8 | use serde::{Deserialize, Serialize}; 9 | use std::collections::HashMap; 10 | 11 | /// Trading error 12 | #[derive(Debug, err_derive::Error)] 13 | pub enum TradingError { 14 | /// Transaction error 15 | #[error(display = "transaction error")] 16 | TransactionError, 17 | /// Loyalty level is not high enough to purchase this item. 18 | #[error(display = "bad loyalty level")] 19 | BadLoyaltyLevel, 20 | } 21 | 22 | /// Trader info 23 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 24 | pub struct Trader { 25 | /// Trader ID 26 | #[serde(rename = "_id")] 27 | pub id: String, 28 | /// Trader is working 29 | pub working: bool, 30 | /// ? 31 | pub customization_seller: bool, 32 | /// Trader name 33 | pub name: String, 34 | /// Trader surname 35 | pub surname: String, 36 | /// Trader nickname 37 | pub nickname: String, 38 | /// Trader location 39 | pub location: String, 40 | /// Trader avatar 41 | pub avatar: String, 42 | /// Trader rouble balance 43 | pub balance_rub: u64, 44 | /// Trader dollar balance 45 | pub balance_dol: u64, 46 | /// Trader euro balance 47 | pub balance_eur: u64, 48 | /// ? 49 | pub display: bool, 50 | /// Trader discount 51 | pub discount: i64, 52 | /// Trader discount expiry 53 | pub discount_end: i64, 54 | /// ? 55 | pub buyer_up: bool, 56 | /// Trader currency 57 | pub currency: Currency, 58 | /// Resupply time 59 | pub supply_next_time: u64, 60 | /// Trader repair offer 61 | pub repair: Repair, 62 | /// Trader insurance offer 63 | pub insurance: Insurance, 64 | /// Trader grid height 65 | #[serde(rename = "gridHeight")] 66 | pub grid_height: u64, 67 | /// Trader loyalty 68 | pub loyalty: Loyalty, 69 | /// Unknown type 70 | pub sell_category: Vec, 71 | } 72 | 73 | /// Trader's repair stats 74 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 75 | pub struct Repair { 76 | /// Repair is available 77 | pub availability: bool, 78 | /// Repair quality 79 | pub quality: String, 80 | /// Item IDs excluded from repair. 81 | pub excluded_id_list: Vec, 82 | /// Category IDs excluded from repair. 83 | pub excluded_category: Vec, 84 | /// Currency 85 | pub currency: Option, 86 | /// ? 87 | pub currency_coefficient: Option, 88 | /// Repair price rate 89 | pub price_rate: u64, 90 | } 91 | 92 | /// Trader currency 93 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 94 | pub enum Currency { 95 | /// Rouble 96 | #[serde(rename = "RUB")] 97 | Rouble, 98 | /// US Dollar 99 | #[serde(rename = "USD")] 100 | Dollar, 101 | /// Euro 102 | #[serde(rename = "EUR")] 103 | Euro, 104 | } 105 | 106 | /// Trader's insurance offer 107 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 108 | pub struct Insurance { 109 | /// Insurance is available 110 | pub availability: bool, 111 | /// Minimum cost to insure 112 | pub min_payment: u64, 113 | /// Minimum return time in hours. 114 | pub min_return_hour: u64, 115 | /// Maximum return time in hours. 116 | pub max_return_hour: u64, 117 | /// Maximum storage time in hours. 118 | pub max_storage_time: u64, 119 | /// Categories IDs excluded from insurance. 120 | pub excluded_category: Vec, 121 | } 122 | 123 | /// Trader loyalty 124 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 125 | #[serde(rename_all = "camelCase")] 126 | pub struct Loyalty { 127 | /// Current loyalty level 128 | pub current_level: u64, 129 | /// Current loyalty standing 130 | pub current_standing: f64, 131 | /// Amount spent on trader 132 | pub current_sales_sum: u64, 133 | /// All loyalty levels 134 | pub loyalty_levels: HashMap, 135 | } 136 | 137 | /// Trader loyalty level 138 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 139 | #[serde(rename_all = "camelCase")] 140 | pub struct LoyaltyLevel { 141 | /// Minimum level 142 | pub min_level: u64, 143 | /// Minimum sales amount 144 | pub min_sales_sum: u64, 145 | /// Minimum standing 146 | pub min_standing: f64, 147 | } 148 | 149 | #[derive(Debug, Deserialize)] 150 | struct TradersResponse { 151 | #[serde(flatten)] 152 | error: ErrorResponse, 153 | data: Option>, 154 | } 155 | 156 | #[derive(Debug, Deserialize)] 157 | struct TraderResponse { 158 | #[serde(flatten)] 159 | error: ErrorResponse, 160 | data: Option, 161 | } 162 | 163 | #[derive(Debug, Deserialize)] 164 | struct TraderItemsResponse { 165 | #[serde(flatten)] 166 | error: ErrorResponse, 167 | data: Option, 168 | } 169 | 170 | #[derive(Debug, Deserialize)] 171 | struct TraderItems { 172 | items: Vec, 173 | barter_scheme: HashMap>>, 174 | loyal_level_items: HashMap, 175 | } 176 | 177 | #[derive(Debug, Deserialize)] 178 | struct TraderPricesResponse { 179 | #[serde(flatten)] 180 | error: ErrorResponse, 181 | data: Option>>>, 182 | } 183 | 184 | /// Trader item price 185 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 186 | pub struct Price { 187 | /// Item localization schema ID 188 | #[serde(rename = "_tpl")] 189 | pub schema_id: String, 190 | /// Item count 191 | pub count: f64, 192 | } 193 | 194 | /// Item for trade 195 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 196 | pub struct TraderItem { 197 | /// Item ID 198 | pub id: String, 199 | /// Item localization schema ID 200 | pub schema_id: String, 201 | /// Item attachments/options 202 | pub upd: Option, 203 | /// Item price 204 | pub price: Vec, 205 | /// Loyalty level 206 | pub loyalty_level: u8, 207 | } 208 | 209 | #[derive(Debug, Serialize)] 210 | struct TradeItemRequest<'a> { 211 | #[serde(rename = "Action")] 212 | action: &'a str, 213 | #[serde(rename = "type")] 214 | trade_type: &'a str, 215 | #[serde(rename = "tid")] 216 | trader_id: &'a str, 217 | item_id: &'a str, 218 | count: u64, 219 | scheme_id: u64, 220 | scheme_items: &'a [BarterItem], 221 | } 222 | 223 | #[derive(Debug, Deserialize)] 224 | struct TradeResponse { 225 | #[serde(flatten)] 226 | error: ErrorResponse, 227 | data: serde_json::Value, 228 | } 229 | 230 | #[derive(Debug, Serialize)] 231 | struct SellItemRequest<'a> { 232 | #[serde(rename = "Action")] 233 | action: &'a str, 234 | #[serde(rename = "type")] 235 | trade_type: &'a str, 236 | #[serde(rename = "tid")] 237 | trader_id: &'a str, 238 | items: &'a [SellItem], 239 | } 240 | 241 | #[derive(Debug, Serialize)] 242 | struct SellItem { 243 | id: String, 244 | count: u64, 245 | scheme_id: u64, 246 | } 247 | 248 | #[derive(Debug, Deserialize)] 249 | struct SellResponse { 250 | #[serde(flatten)] 251 | error: ErrorResponse, 252 | } 253 | 254 | impl Tarkov { 255 | /// Get a list of all traders. 256 | pub async fn get_traders(&self) -> Result> { 257 | let url = format!("{}/client/trading/api/getTradersList", TRADING_ENDPOINT); 258 | let res: TradersResponse = self.post_json(&url, &{}).await?; 259 | 260 | handle_error(res.error, res.data) 261 | } 262 | 263 | /// Get a trader by ID. 264 | pub async fn get_trader(&self, trader_id: &str) -> Result { 265 | if trader_id.is_empty() { 266 | return Err(Error::InvalidParameters); 267 | } 268 | 269 | let url = format!( 270 | "{}/client/trading/api/getTrader/{}", 271 | TRADING_ENDPOINT, trader_id 272 | ); 273 | let res: TraderResponse = self.post_json(&url, &{}).await?; 274 | 275 | handle_error(res.error, res.data) 276 | } 277 | 278 | async fn get_trader_items_raw(&self, trader_id: &str) -> Result { 279 | let url = format!( 280 | "{}/client/trading/api/getTraderAssort/{}", 281 | TRADING_ENDPOINT, trader_id 282 | ); 283 | let res: TraderItemsResponse = self.post_json(&url, &{}).await?; 284 | 285 | handle_error(res.error, res.data) 286 | } 287 | 288 | async fn get_trader_prices_raw( 289 | &self, 290 | trader_id: &str, 291 | ) -> Result>>> { 292 | let url = format!( 293 | "{}/client/trading/api/getUserAssortPrice/trader/{}", 294 | TRADING_ENDPOINT, trader_id 295 | ); 296 | let res: TraderPricesResponse = self.post_json(&url, &{}).await?; 297 | 298 | handle_error(res.error, res.data) 299 | } 300 | 301 | /// Get a list of items for sale by trader ID. 302 | pub async fn get_trader_items(&self, trader_id: &str) -> Result> { 303 | if trader_id.is_empty() { 304 | return Err(Error::InvalidParameters); 305 | } 306 | 307 | let mut result: Vec = Vec::new(); 308 | 309 | let items = self.get_trader_items_raw(trader_id).await?; 310 | let prices = self.get_trader_prices_raw(trader_id).await?; 311 | 312 | for item in items.items { 313 | // TODO: Properly deal with parent/children items 314 | if item.parent_id != Some("hideout".to_string()) { 315 | continue; 316 | } 317 | 318 | let loyalty_level = items 319 | .loyal_level_items 320 | .get(&item.id) 321 | .expect("Loyalty level could not be mapped."); 322 | let price = { 323 | let barter_or_price = match items.barter_scheme.get(&item.id) { 324 | None => prices 325 | .get(&item.id) 326 | .expect("Item price could not be mapped."), 327 | Some(barter) => barter, 328 | }; 329 | 330 | barter_or_price.get(0) 331 | }; 332 | 333 | let trader_item = TraderItem { 334 | id: item.id, 335 | schema_id: item.schema_id, 336 | upd: item.upd, 337 | price: price.expect("Item price could not be mapped.").clone(), 338 | loyalty_level: *loyalty_level, 339 | }; 340 | 341 | result.push(trader_item); 342 | } 343 | 344 | Ok(result) 345 | } 346 | 347 | /// Trade items with traders. 348 | /// 349 | /// All trades, including cash trades, are considered bartering. `barter_items` expects a 350 | /// list of items from your inventory that matches the item price. 351 | pub async fn trade_item( 352 | &self, 353 | trader_id: &str, 354 | item_id: &str, 355 | quantity: u64, 356 | barter_items: &[BarterItem], 357 | ) -> Result { 358 | if trader_id.is_empty() || item_id.is_empty() || quantity == 0 || barter_items.is_empty() { 359 | return Err(Error::InvalidParameters); 360 | } 361 | 362 | let url = format!("{}/client/game/profile/items/moving", PROD_ENDPOINT); 363 | let body = MoveItemRequest { 364 | data: &[TradeItemRequest { 365 | action: "TradingConfirm", 366 | trade_type: "buy_from_trader", 367 | trader_id, 368 | item_id, 369 | count: quantity, 370 | scheme_id: 0, 371 | scheme_items: barter_items, 372 | }], 373 | tm: 0, 374 | }; 375 | 376 | let res: TradeResponse = self.post_json(&url, &body).await?; 377 | handle_error2(res.error)?; 378 | 379 | let res: RagfairResponseData = Deserialize::deserialize(res.data)?; 380 | if !res.errors.is_empty() { 381 | let error = &res.errors[0]; 382 | return Err(Error::UnknownAPIError(error.code)); 383 | } 384 | 385 | let items: InventoryUpdate = Deserialize::deserialize(res.items)?; 386 | Ok(items) 387 | } 388 | 389 | /// Sell items to trader. 390 | pub async fn sell_item( 391 | &self, 392 | trader_id: &str, 393 | item_id: &str, 394 | quantity: u64, 395 | ) -> Result { 396 | if trader_id.is_empty() || item_id.is_empty() || quantity == 0 { 397 | return Err(Error::InvalidParameters); 398 | } 399 | 400 | let url = format!("{}/client/game/profile/items/moving", PROD_ENDPOINT); 401 | let body = MoveItemRequest { 402 | data: &[SellItemRequest { 403 | action: "TradingConfirm", 404 | trade_type: "sell_to_trader", 405 | trader_id, 406 | items: &[SellItem { 407 | id: item_id.to_string(), 408 | count: quantity, 409 | scheme_id: 0, 410 | }], 411 | }], 412 | tm: 0, 413 | }; 414 | 415 | let res: TradeResponse = self.post_json(&url, &body).await?; 416 | handle_error2(res.error)?; 417 | 418 | let res: RagfairResponseData = Deserialize::deserialize(res.data)?; 419 | if !res.errors.is_empty() { 420 | let error = &res.errors[0]; 421 | return Err(Error::UnknownAPIError(error.code)); 422 | } 423 | 424 | let items: InventoryUpdate = Deserialize::deserialize(res.items)?; 425 | Ok(items) 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /src/profile.rs: -------------------------------------------------------------------------------- 1 | use crate::{handle_error, Error, ErrorResponse, Result, Tarkov, PROD_ENDPOINT}; 2 | 3 | use crate::bad_json::deserialize_integer_to_string; 4 | use crate::inventory::Item; 5 | use crate::ragfair::Offer; 6 | use serde::{Deserialize, Serialize}; 7 | use std::collections::HashMap; 8 | 9 | #[derive(Debug, Deserialize)] 10 | struct ProfileResponse { 11 | #[serde(flatten)] 12 | error: ErrorResponse, 13 | data: Option>, 14 | } 15 | 16 | /// Profile 17 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 18 | #[serde(rename_all = "PascalCase")] 19 | pub struct Profile { 20 | /// Profile ID 21 | #[serde(rename = "_id")] 22 | pub id: String, 23 | /// ? 24 | #[serde(rename = "aid")] 25 | pub aid: u64, 26 | /// SCAV profile ID 27 | #[serde(rename = "savage")] 28 | pub savage: Option, 29 | /// Profile info 30 | pub info: Info, 31 | /// Character customization 32 | pub customization: Customization, 33 | /// Character health 34 | pub health: Health, 35 | /// Inventory 36 | pub inventory: Inventory, 37 | /// Skills 38 | pub skills: Skills, 39 | /// Stats 40 | pub stats: Stats, 41 | /// Encyclopedia 42 | pub encyclopedia: HashMap, 43 | /// ? 44 | pub condition_counters: ConditionCounters, 45 | /// ? 46 | pub backend_counters: HashMap, 47 | /// Insured items 48 | pub insured_items: Vec, 49 | /// Unimplemented type 50 | pub hideout: serde_json::Value, 51 | /// Unknown type 52 | pub notes: serde_json::Value, 53 | /// Bonuses? 54 | pub bonuses: Vec, 55 | /// Active quests 56 | pub quests: Vec, 57 | /// Flea market stats 58 | #[serde(rename = "RagfairInfo")] 59 | pub ragfair: Ragfair, 60 | /// Unknown type 61 | pub trader_standings: serde_json::Value, 62 | /// Item wishlist 63 | #[serde(rename = "WishList")] 64 | pub wishlist: Vec, 65 | } 66 | 67 | /// Profile info 68 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 69 | #[serde(rename_all = "PascalCase")] 70 | pub struct Info { 71 | /// Profile nickname 72 | pub nickname: String, 73 | /// Profile lowercase nickname 74 | pub lower_nickname: String, 75 | /// Profile side 76 | pub side: Side, 77 | /// Profile voice 78 | pub voice: String, 79 | /// Profile level 80 | pub level: u64, 81 | /// Profile experience points 82 | pub experience: u64, 83 | /// Profile registration timestamp 84 | pub registration_date: u64, 85 | /// Game version 86 | pub game_version: String, 87 | /// Profile type 88 | pub account_type: u64, 89 | /// ? 90 | #[serde(deserialize_with = "deserialize_integer_to_string")] 91 | pub member_category: String, 92 | /// ? 93 | #[serde(rename = "lockedMoveCommands")] 94 | pub locked_move_commands: bool, 95 | /// SCAV cooldown timestamp 96 | pub savage_lock_time: u64, 97 | /// Last time played as SCAV 98 | pub last_time_played_as_savage: u64, 99 | /// Profile settings 100 | pub settings: Settings, 101 | /// ? 102 | pub need_wipe: bool, 103 | /// ? 104 | pub global_wipe: bool, 105 | /// Profile nickname change timestamp 106 | pub nickname_change_date: u64, 107 | /// Unknown type 108 | pub bans: serde_json::Value, 109 | } 110 | 111 | /// Faction, team or side. 112 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 113 | pub enum Side { 114 | /// BEAR faction 115 | Bear, 116 | /// USEC faction 117 | Usec, 118 | /// SCAV 119 | Savage, 120 | } 121 | 122 | /// Profile settings 123 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 124 | #[serde(rename_all = "PascalCase")] 125 | pub struct Settings { 126 | /// ? 127 | pub role: Option, 128 | /// ? 129 | pub bot_difficulty: Option, 130 | /// ? 131 | pub experience: Option, 132 | } 133 | 134 | /// Player customization 135 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 136 | #[serde(rename_all = "PascalCase")] 137 | pub struct Customization { 138 | /// Head customization 139 | pub head: String, 140 | /// Body customization 141 | pub body: String, 142 | /// Feet customization 143 | pub feet: String, 144 | /// Hands customization 145 | pub hands: String, 146 | } 147 | 148 | /// Player health 149 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 150 | #[serde(rename_all = "PascalCase")] 151 | pub struct Health { 152 | /// Hydration level 153 | pub hydration: HealthLevel, 154 | /// Energy level 155 | pub energy: HealthLevel, 156 | /// Body parts health 157 | pub body_parts: BodyParts, 158 | /// ? 159 | pub update_time: u64, 160 | } 161 | 162 | /// Health level 163 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 164 | #[serde(rename_all = "PascalCase")] 165 | pub struct HealthLevel { 166 | /// Current health 167 | pub current: f64, 168 | /// Maximum health 169 | pub maximum: f64, 170 | } 171 | 172 | /// Body health 173 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 174 | #[serde(rename_all = "PascalCase")] 175 | pub struct BodyParts { 176 | /// Head health 177 | pub head: Head, 178 | /// Chest health 179 | pub chest: Chest, 180 | /// Stomach health 181 | pub stomach: Stomach, 182 | /// Left arm health 183 | pub left_arm: LeftArm, 184 | /// Right arm health 185 | pub right_arm: RightArm, 186 | /// Left leg health 187 | pub left_leg: LeftLeg, 188 | /// Right arm health 189 | pub right_leg: RightLeg, 190 | } 191 | 192 | /// Head health 193 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 194 | #[serde(rename_all = "PascalCase")] 195 | pub struct Head { 196 | /// Health 197 | pub health: HealthLevel, 198 | } 199 | 200 | /// Chest health 201 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 202 | #[serde(rename_all = "PascalCase")] 203 | pub struct Chest { 204 | /// Health 205 | pub health: HealthLevel, 206 | } 207 | 208 | /// Stomach health 209 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 210 | #[serde(rename_all = "PascalCase")] 211 | pub struct Stomach { 212 | /// Health 213 | pub health: HealthLevel, 214 | } 215 | 216 | /// Left arm health 217 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 218 | #[serde(rename_all = "PascalCase")] 219 | pub struct LeftArm { 220 | /// Health 221 | pub health: HealthLevel, 222 | } 223 | 224 | /// Right arm health 225 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 226 | #[serde(rename_all = "PascalCase")] 227 | pub struct RightArm { 228 | /// Health 229 | pub health: HealthLevel, 230 | } 231 | 232 | /// Left leg health 233 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 234 | #[serde(rename_all = "PascalCase")] 235 | pub struct LeftLeg { 236 | /// Health 237 | pub health: HealthLevel, 238 | } 239 | 240 | /// Right leg health 241 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 242 | #[serde(rename_all = "PascalCase")] 243 | pub struct RightLeg { 244 | /// Health 245 | pub health: HealthLevel, 246 | } 247 | 248 | /// Profile inventory 249 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 250 | #[serde(rename_all = "camelCase")] 251 | pub struct Inventory { 252 | /// Items 253 | pub items: Vec, 254 | /// Equipment 255 | pub equipment: String, 256 | /// Stash 257 | pub stash: Option, 258 | /// ? 259 | pub quest_raid_items: String, 260 | /// ? 261 | pub quest_stash_items: String, 262 | /// Unknown type 263 | pub fast_panel: serde_json::Value, 264 | } 265 | 266 | /// Player skills 267 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 268 | #[serde(rename_all = "PascalCase")] 269 | pub struct Skills { 270 | /// Common skills 271 | pub common: Vec, 272 | /// Master skills 273 | pub mastering: Vec, 274 | /// Skill points 275 | pub points: f64, 276 | } 277 | 278 | /// Common skill 279 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 280 | #[serde(rename_all = "PascalCase")] 281 | pub struct CommonSkill { 282 | /// Skill ID 283 | pub id: String, 284 | /// Skill progress 285 | pub progress: f64, 286 | /// Points earned during session 287 | pub points_earned_during_session: Option, 288 | /// ? 289 | pub last_access: i64, 290 | } 291 | 292 | /// Master skill 293 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 294 | #[serde(rename_all = "PascalCase")] 295 | pub struct MasteringSkill { 296 | /// Skill ID 297 | pub id: String, 298 | /// Skill progress 299 | pub progress: u64, 300 | } 301 | 302 | /// Player statistics 303 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 304 | #[serde(rename_all = "PascalCase")] 305 | pub struct Stats { 306 | /// Session stats counters 307 | pub session_counters: SessionCounters, 308 | /// Overall stats counters 309 | pub overall_counters: OverallCounters, 310 | /// Session experience multiplier? 311 | pub session_experience_mult: f64, 312 | /// Experience bonus multiplier? 313 | pub experience_bonus_mult: f64, 314 | /// Total session experience 315 | pub total_session_experience: u64, 316 | /// Last session timestamp 317 | pub last_session_date: u64, 318 | /// ? 319 | pub aggressor: Option, 320 | /// Total game time 321 | pub total_in_game_time: u64, 322 | /// Survivor class 323 | pub survivor_class: String, 324 | /// Unknown type 325 | pub dropped_items: serde_json::Value, 326 | /// Unknown type 327 | pub found_in_raid_items: serde_json::Value, 328 | /// Unknown type 329 | pub victims: Vec, 330 | /// Unknown type 331 | pub carried_quest_items: serde_json::Value, 332 | } 333 | 334 | /// Session stats counter 335 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 336 | #[serde(rename_all = "PascalCase")] 337 | pub struct SessionCounters { 338 | /// Statistics 339 | pub items: Vec, 340 | } 341 | 342 | /// Overall stats counter 343 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 344 | #[serde(rename_all = "PascalCase")] 345 | pub struct OverallCounters { 346 | /// Statistics 347 | pub items: Vec, 348 | } 349 | 350 | /// Statistics 351 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 352 | #[serde(rename_all = "PascalCase")] 353 | pub struct SessionItem { 354 | /// Statistic key 355 | pub key: Vec, 356 | /// Statistic value 357 | pub value: u64, 358 | } 359 | 360 | /// Aggressor 361 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 362 | #[serde(rename_all = "PascalCase")] 363 | pub struct Aggressor { 364 | /// Aggressor name 365 | pub name: String, 366 | /// Aggressor side 367 | pub side: Side, 368 | /// Aggressor body part 369 | pub body_part: String, 370 | /// Aggressor head segment 371 | pub head_segment: Option, 372 | /// Aggressor weapon name 373 | pub weapon_name: String, 374 | /// Aggressor category 375 | pub category: String, 376 | } 377 | 378 | /// Victim 379 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 380 | #[serde(rename_all = "PascalCase")] 381 | pub struct Victim { 382 | /// Victim name 383 | pub name: String, 384 | /// Victim side 385 | pub side: Side, 386 | /// Victim death time 387 | pub time: String, 388 | /// Victim level 389 | pub level: u64, 390 | /// Body part shot 391 | pub body_part: String, 392 | /// Weapon used to kill 393 | pub weapon: String, 394 | } 395 | 396 | /// ? 397 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 398 | #[serde(rename_all = "PascalCase")] 399 | pub struct ConditionCounters { 400 | /// Counters 401 | pub counters: Vec, 402 | } 403 | 404 | /// ? 405 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 406 | #[serde(rename_all = "camelCase")] 407 | pub struct ConditionCounter { 408 | /// Counter ID 409 | pub id: String, 410 | /// Counter value 411 | pub value: u64, 412 | } 413 | 414 | /// ? 415 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 416 | #[serde(rename_all = "camelCase")] 417 | pub struct BackendCounter { 418 | /// Counter ID 419 | pub id: String, 420 | /// ? 421 | pub qid: String, 422 | /// Counter value 423 | pub value: u64, 424 | } 425 | 426 | /// Insured item 427 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 428 | #[serde(rename_all = "camelCase")] 429 | pub struct InsuredItem { 430 | /// Insurance ID 431 | #[serde(rename = "tid")] 432 | id: String, 433 | /// Insured item ID 434 | item_id: String, 435 | } 436 | 437 | /// Bonus? 438 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 439 | #[serde(rename_all = "camelCase")] 440 | pub struct Bonus { 441 | /// ? 442 | #[serde(rename = "type")] 443 | pub bonus_type: String, 444 | /// ? 445 | pub template_id: Option, 446 | /// ? 447 | pub value: Option, 448 | /// ? 449 | pub passive: Option, 450 | /// ? 451 | pub visible: Option, 452 | /// ? 453 | pub production: Option, 454 | /// ? 455 | pub filter: Option>, 456 | /// ? 457 | pub id: Option, 458 | /// ? 459 | pub icon: Option, 460 | } 461 | 462 | /// Quest 463 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 464 | #[serde(rename_all = "camelCase")] 465 | pub struct Quest { 466 | /// Quest ID 467 | #[serde(rename = "qid")] 468 | pub id: String, 469 | /// Quest start time 470 | pub start_time: u64, 471 | /// Quest status 472 | pub status: u64, 473 | /// Quest status timers 474 | pub status_timers: HashMap, 475 | } 476 | 477 | /// Flea market profile 478 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 479 | #[serde(rename_all = "camelCase")] 480 | pub struct Ragfair { 481 | /// Market rating 482 | pub rating: f64, 483 | /// Market rating is growing 484 | pub is_rating_growing: bool, 485 | /// Active offers (items for sale) 486 | pub offers: Vec, 487 | } 488 | 489 | /// Profile error. 490 | #[derive(Debug, err_derive::Error)] 491 | pub enum ProfileError { 492 | /// Invalid user ID selected. 493 | #[error(display = "invalid user id selected")] 494 | InvalidUserID, 495 | /// Failed selecting profile. 496 | #[error(display = "select profile failed")] 497 | SelectProfileFail, 498 | } 499 | 500 | #[derive(Debug, Serialize)] 501 | struct SelectRequest<'a> { 502 | uid: &'a str, 503 | } 504 | 505 | #[derive(Debug, Deserialize)] 506 | struct SelectResponse { 507 | #[serde(flatten)] 508 | error: ErrorResponse, 509 | data: Option, 510 | } 511 | 512 | /// Profile select result 513 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 514 | #[serde(rename_all = "camelCase")] 515 | struct ProfileSelected { 516 | /// Profile status 517 | pub status: String, 518 | /// Profile notifier 519 | pub notifier: Notifier, 520 | /// Profile notifier server 521 | pub notifier_server: String, 522 | } 523 | 524 | /// Profile notifier 525 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 526 | pub struct Notifier { 527 | /// Notifier server 528 | pub server: String, 529 | /// Notifier channel ID 530 | pub channel_id: String, 531 | /// Notifier URL 532 | pub url: String, 533 | } 534 | 535 | impl Tarkov { 536 | /// Get a list of account's profiles. 537 | pub async fn get_profiles(&self) -> Result> { 538 | let url = format!("{}/client/game/profile/list", PROD_ENDPOINT); 539 | let res: ProfileResponse = self.post_json(&url, &{}).await?; 540 | 541 | handle_error(res.error, res.data) 542 | } 543 | 544 | /// Select a profile by user ID. 545 | pub async fn select_profile(&self, user_id: &str) -> Result<()> { 546 | if user_id.is_empty() { 547 | return Err(Error::InvalidParameters); 548 | } 549 | 550 | let url = format!("{}/client/game/profile/select", PROD_ENDPOINT); 551 | let res: SelectResponse = self 552 | .post_json(&url, &SelectRequest { uid: user_id }) 553 | .await?; 554 | 555 | let res = handle_error(res.error, res.data)?; 556 | if res.status != "ok" { 557 | return Err(ProfileError::SelectProfileFail)?; 558 | } 559 | 560 | Ok(()) 561 | } 562 | } 563 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "aho-corasick" 10 | version = "0.7.9" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "atty" 18 | version = "0.2.14" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 24 | ] 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.2.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "bytes" 38 | version = "0.5.4" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | 41 | [[package]] 42 | name = "c2-chacha" 43 | version = "0.2.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | dependencies = [ 46 | "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "cc" 51 | version = "1.0.50" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "0.1.10" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "core-foundation" 61 | version = "0.7.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 66 | ] 67 | 68 | [[package]] 69 | name = "core-foundation-sys" 70 | version = "0.7.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | 73 | [[package]] 74 | name = "crc32fast" 75 | version = "1.2.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "env_logger" 83 | version = "0.7.1" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | dependencies = [ 86 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 91 | ] 92 | 93 | [[package]] 94 | name = "err-derive" 95 | version = "0.2.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | dependencies = [ 98 | "proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 100 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 104 | ] 105 | 106 | [[package]] 107 | name = "flate2" 108 | version = "1.0.13" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | dependencies = [ 111 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "fnv" 120 | version = "1.0.6" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | 123 | [[package]] 124 | name = "foreign-types" 125 | version = "0.3.2" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | dependencies = [ 128 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 129 | ] 130 | 131 | [[package]] 132 | name = "foreign-types-shared" 133 | version = "0.1.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | 136 | [[package]] 137 | name = "fuchsia-zircon" 138 | version = "0.3.3" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | dependencies = [ 141 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 143 | ] 144 | 145 | [[package]] 146 | name = "fuchsia-zircon-sys" 147 | version = "0.3.3" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | 150 | [[package]] 151 | name = "futures-channel" 152 | version = "0.3.4" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | dependencies = [ 155 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 156 | ] 157 | 158 | [[package]] 159 | name = "futures-core" 160 | version = "0.3.4" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | 163 | [[package]] 164 | name = "futures-sink" 165 | version = "0.3.4" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | 168 | [[package]] 169 | name = "futures-task" 170 | version = "0.3.4" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | 173 | [[package]] 174 | name = "futures-util" 175 | version = "0.3.4" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | dependencies = [ 178 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 179 | "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", 181 | ] 182 | 183 | [[package]] 184 | name = "getrandom" 185 | version = "0.1.14" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 191 | ] 192 | 193 | [[package]] 194 | name = "h2" 195 | version = "0.2.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | dependencies = [ 198 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 199 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 200 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 206 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 208 | "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 209 | ] 210 | 211 | [[package]] 212 | name = "hermit-abi" 213 | version = "0.1.8" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | dependencies = [ 216 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 217 | ] 218 | 219 | [[package]] 220 | name = "http" 221 | version = "0.2.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | dependencies = [ 224 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 227 | ] 228 | 229 | [[package]] 230 | name = "http-body" 231 | version = "0.3.1" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | dependencies = [ 234 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 235 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 236 | ] 237 | 238 | [[package]] 239 | name = "httparse" 240 | version = "1.3.4" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | 243 | [[package]] 244 | name = "humantime" 245 | version = "1.3.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | dependencies = [ 248 | "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 249 | ] 250 | 251 | [[package]] 252 | name = "hyper" 253 | version = "0.13.3" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | dependencies = [ 256 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 259 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 260 | "h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 262 | "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 263 | "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 264 | "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 265 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 266 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 271 | "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 272 | ] 273 | 274 | [[package]] 275 | name = "hyper-tls" 276 | version = "0.4.1" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | dependencies = [ 279 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "hyper 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 283 | "tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 284 | ] 285 | 286 | [[package]] 287 | name = "indexmap" 288 | version = "1.3.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | dependencies = [ 291 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 292 | ] 293 | 294 | [[package]] 295 | name = "iovec" 296 | version = "0.1.4" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | dependencies = [ 299 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 300 | ] 301 | 302 | [[package]] 303 | name = "itoa" 304 | version = "0.4.5" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | 307 | [[package]] 308 | name = "kernel32-sys" 309 | version = "0.2.2" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | dependencies = [ 312 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 314 | ] 315 | 316 | [[package]] 317 | name = "lazy_static" 318 | version = "1.4.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | 321 | [[package]] 322 | name = "libc" 323 | version = "0.2.67" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | 326 | [[package]] 327 | name = "libz-sys" 328 | version = "1.0.25" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | dependencies = [ 331 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 332 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 333 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", 334 | "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 335 | ] 336 | 337 | [[package]] 338 | name = "log" 339 | version = "0.4.8" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | dependencies = [ 342 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 343 | ] 344 | 345 | [[package]] 346 | name = "md5" 347 | version = "0.7.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | 350 | [[package]] 351 | name = "memchr" 352 | version = "2.3.3" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | 355 | [[package]] 356 | name = "miniz_oxide" 357 | version = "0.3.6" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | dependencies = [ 360 | "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 361 | ] 362 | 363 | [[package]] 364 | name = "mio" 365 | version = "0.6.21" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | dependencies = [ 368 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 369 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 370 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 371 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 372 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 373 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 374 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 375 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 376 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 377 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 378 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 379 | ] 380 | 381 | [[package]] 382 | name = "miow" 383 | version = "0.2.1" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | dependencies = [ 386 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 387 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 388 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 390 | ] 391 | 392 | [[package]] 393 | name = "native-tls" 394 | version = "0.2.4" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | dependencies = [ 397 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 398 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 399 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 400 | "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", 401 | "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 402 | "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", 403 | "schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 404 | "security-framework 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 405 | "security-framework-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 406 | "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 407 | ] 408 | 409 | [[package]] 410 | name = "net2" 411 | version = "0.2.33" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | dependencies = [ 414 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 415 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 416 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 417 | ] 418 | 419 | [[package]] 420 | name = "openssl" 421 | version = "0.10.28" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | dependencies = [ 424 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 425 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 426 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 427 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 428 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 429 | "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", 430 | ] 431 | 432 | [[package]] 433 | name = "openssl-probe" 434 | version = "0.1.2" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | 437 | [[package]] 438 | name = "openssl-sys" 439 | version = "0.9.54" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | dependencies = [ 442 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 443 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 444 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 445 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", 446 | "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 447 | ] 448 | 449 | [[package]] 450 | name = "pin-project" 451 | version = "0.4.8" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | dependencies = [ 454 | "pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 455 | ] 456 | 457 | [[package]] 458 | name = "pin-project-internal" 459 | version = "0.4.8" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | dependencies = [ 462 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 463 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 464 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 465 | ] 466 | 467 | [[package]] 468 | name = "pin-project-lite" 469 | version = "0.1.4" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | 472 | [[package]] 473 | name = "pin-utils" 474 | version = "0.1.0-alpha.4" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | 477 | [[package]] 478 | name = "pkg-config" 479 | version = "0.3.17" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | 482 | [[package]] 483 | name = "ppv-lite86" 484 | version = "0.2.6" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | 487 | [[package]] 488 | name = "proc-macro-error" 489 | version = "0.4.11" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | dependencies = [ 492 | "proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 493 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 494 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 495 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 496 | "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 497 | ] 498 | 499 | [[package]] 500 | name = "proc-macro-error-attr" 501 | version = "0.4.11" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | dependencies = [ 504 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 505 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 506 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 508 | "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 509 | ] 510 | 511 | [[package]] 512 | name = "proc-macro2" 513 | version = "1.0.9" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | dependencies = [ 516 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 517 | ] 518 | 519 | [[package]] 520 | name = "quick-error" 521 | version = "1.2.3" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | 524 | [[package]] 525 | name = "quote" 526 | version = "1.0.3" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | dependencies = [ 529 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 530 | ] 531 | 532 | [[package]] 533 | name = "rand" 534 | version = "0.7.3" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | dependencies = [ 537 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 538 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 539 | "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 540 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 541 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 542 | ] 543 | 544 | [[package]] 545 | name = "rand_chacha" 546 | version = "0.2.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | dependencies = [ 549 | "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 550 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 551 | ] 552 | 553 | [[package]] 554 | name = "rand_core" 555 | version = "0.5.1" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | dependencies = [ 558 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 559 | ] 560 | 561 | [[package]] 562 | name = "rand_hc" 563 | version = "0.2.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | dependencies = [ 566 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 567 | ] 568 | 569 | [[package]] 570 | name = "redox_syscall" 571 | version = "0.1.56" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | 574 | [[package]] 575 | name = "regex" 576 | version = "1.3.4" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | dependencies = [ 579 | "aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", 580 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 581 | "regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", 582 | "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 583 | ] 584 | 585 | [[package]] 586 | name = "regex-syntax" 587 | version = "0.6.16" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | 590 | [[package]] 591 | name = "remove_dir_all" 592 | version = "0.5.2" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | dependencies = [ 595 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 596 | ] 597 | 598 | [[package]] 599 | name = "rustversion" 600 | version = "1.0.2" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | dependencies = [ 603 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 604 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 605 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 606 | ] 607 | 608 | [[package]] 609 | name = "ryu" 610 | version = "1.0.2" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | 613 | [[package]] 614 | name = "schannel" 615 | version = "0.1.17" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | dependencies = [ 618 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 619 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 620 | ] 621 | 622 | [[package]] 623 | name = "security-framework" 624 | version = "0.4.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | dependencies = [ 627 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 628 | "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 629 | "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 630 | "security-framework-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 631 | ] 632 | 633 | [[package]] 634 | name = "security-framework-sys" 635 | version = "0.4.1" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | dependencies = [ 638 | "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 639 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 640 | ] 641 | 642 | [[package]] 643 | name = "serde" 644 | version = "1.0.104" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | dependencies = [ 647 | "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 648 | ] 649 | 650 | [[package]] 651 | name = "serde_derive" 652 | version = "1.0.104" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | dependencies = [ 655 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 656 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 657 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 658 | ] 659 | 660 | [[package]] 661 | name = "serde_json" 662 | version = "1.0.48" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | dependencies = [ 665 | "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 666 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 667 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 668 | ] 669 | 670 | [[package]] 671 | name = "serde_repr" 672 | version = "0.1.5" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | dependencies = [ 675 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 676 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 677 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 678 | ] 679 | 680 | [[package]] 681 | name = "slab" 682 | version = "0.4.2" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | 685 | [[package]] 686 | name = "syn" 687 | version = "1.0.16" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | dependencies = [ 690 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 691 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 692 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 693 | ] 694 | 695 | [[package]] 696 | name = "syn-mid" 697 | version = "0.5.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | dependencies = [ 700 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 701 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 702 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 703 | ] 704 | 705 | [[package]] 706 | name = "synstructure" 707 | version = "0.12.3" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | dependencies = [ 710 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 711 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 712 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 713 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 714 | ] 715 | 716 | [[package]] 717 | name = "tarkov" 718 | version = "0.1.6" 719 | dependencies = [ 720 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 721 | "err-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 722 | "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", 723 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 724 | "hyper 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)", 725 | "hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 726 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 727 | "md5 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 728 | "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 729 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 730 | "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 731 | "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 732 | "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 733 | ] 734 | 735 | [[package]] 736 | name = "tempfile" 737 | version = "3.1.0" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | dependencies = [ 740 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 741 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 742 | "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 743 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 744 | "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 745 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 746 | ] 747 | 748 | [[package]] 749 | name = "termcolor" 750 | version = "1.1.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | dependencies = [ 753 | "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 754 | ] 755 | 756 | [[package]] 757 | name = "thread_local" 758 | version = "1.0.1" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | dependencies = [ 761 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 762 | ] 763 | 764 | [[package]] 765 | name = "time" 766 | version = "0.1.42" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | dependencies = [ 769 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 770 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 771 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 772 | ] 773 | 774 | [[package]] 775 | name = "tokio" 776 | version = "0.2.13" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | dependencies = [ 779 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 780 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 781 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 782 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 783 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 784 | "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", 785 | "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 786 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 787 | "tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 788 | ] 789 | 790 | [[package]] 791 | name = "tokio-macros" 792 | version = "0.2.5" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | dependencies = [ 795 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 796 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 797 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 798 | ] 799 | 800 | [[package]] 801 | name = "tokio-tls" 802 | version = "0.3.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | dependencies = [ 805 | "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 806 | "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 807 | ] 808 | 809 | [[package]] 810 | name = "tokio-util" 811 | version = "0.2.0" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | dependencies = [ 814 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 815 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 816 | "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 817 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 818 | "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 819 | "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 820 | ] 821 | 822 | [[package]] 823 | name = "tower-service" 824 | version = "0.3.0" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | 827 | [[package]] 828 | name = "try-lock" 829 | version = "0.2.2" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | 832 | [[package]] 833 | name = "unicode-xid" 834 | version = "0.2.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | 837 | [[package]] 838 | name = "vcpkg" 839 | version = "0.2.8" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | 842 | [[package]] 843 | name = "version_check" 844 | version = "0.9.1" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | 847 | [[package]] 848 | name = "want" 849 | version = "0.3.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | dependencies = [ 852 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 853 | "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 854 | ] 855 | 856 | [[package]] 857 | name = "wasi" 858 | version = "0.9.0+wasi-snapshot-preview1" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | 861 | [[package]] 862 | name = "winapi" 863 | version = "0.2.8" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | 866 | [[package]] 867 | name = "winapi" 868 | version = "0.3.8" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | dependencies = [ 871 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 872 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 873 | ] 874 | 875 | [[package]] 876 | name = "winapi-build" 877 | version = "0.1.1" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | 880 | [[package]] 881 | name = "winapi-i686-pc-windows-gnu" 882 | version = "0.4.0" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | 885 | [[package]] 886 | name = "winapi-util" 887 | version = "0.1.3" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | dependencies = [ 890 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 891 | ] 892 | 893 | [[package]] 894 | name = "winapi-x86_64-pc-windows-gnu" 895 | version = "0.4.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | 898 | [[package]] 899 | name = "ws2_32-sys" 900 | version = "0.2.1" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | dependencies = [ 903 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 904 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 905 | ] 906 | 907 | [metadata] 908 | "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 909 | "checksum aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec" 910 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 911 | "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 912 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 913 | "checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 914 | "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" 915 | "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 916 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 917 | "checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 918 | "checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 919 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 920 | "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 921 | "checksum err-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "82f46c91bbed409ee74495549acbfcc7fae856e712e1df15afe75d0775eedc6c" 922 | "checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" 923 | "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 924 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 925 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 926 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 927 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 928 | "checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" 929 | "checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" 930 | "checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" 931 | "checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" 932 | "checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" 933 | "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 934 | "checksum h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47" 935 | "checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" 936 | "checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" 937 | "checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 938 | "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 939 | "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 940 | "checksum hyper 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e7b15203263d1faa615f9337d79c1d37959439dc46c2b4faab33286fadc2a1c5" 941 | "checksum hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" 942 | "checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 943 | "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 944 | "checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 945 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 946 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 947 | "checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" 948 | "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" 949 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 950 | "checksum md5 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 951 | "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 952 | "checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" 953 | "checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" 954 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 955 | "checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" 956 | "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 957 | "checksum openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)" = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" 958 | "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 959 | "checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" 960 | "checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" 961 | "checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" 962 | "checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" 963 | "checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" 964 | "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 965 | "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 966 | "checksum proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a" 967 | "checksum proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a" 968 | "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" 969 | "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 970 | "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 971 | "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 972 | "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 973 | "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 974 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 975 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 976 | "checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" 977 | "checksum regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" 978 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 979 | "checksum rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" 980 | "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 981 | "checksum schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" 982 | "checksum security-framework 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "97bbedbe81904398b6ebb054b3e912f99d55807125790f3198ac990d98def5b0" 983 | "checksum security-framework-sys 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "06fd2f23e31ef68dd2328cc383bd493142e46107a3a0e24f7d734e3f3b80fe4c" 984 | "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" 985 | "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" 986 | "checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" 987 | "checksum serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "cd02c7587ec314570041b2754829f84d873ced14a96d1fd1823531e11db40573" 988 | "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 989 | "checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" 990 | "checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" 991 | "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 992 | "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 993 | "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 994 | "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 995 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 996 | "checksum tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" 997 | "checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" 998 | "checksum tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828" 999 | "checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" 1000 | "checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 1001 | "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 1002 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1003 | "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" 1004 | "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" 1005 | "checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1006 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1007 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1008 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1009 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1010 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1011 | "checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" 1012 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1013 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1014 | -------------------------------------------------------------------------------- /src/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::{handle_error, Error, ErrorResponse, Result, Tarkov, PROD_ENDPOINT}; 2 | 3 | use crate::bad_json::{deserialize_integer_to_option_string, StringOrInt}; 4 | use crate::profile::Side; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::HashMap; 7 | 8 | #[derive(Debug, Serialize)] 9 | #[serde(rename_all = "camelCase")] 10 | struct Request { 11 | crc: u64, 12 | } 13 | 14 | #[derive(Debug, Deserialize)] 15 | #[serde(rename_all = "camelCase")] 16 | struct ItemsResponse { 17 | #[serde(flatten)] 18 | error: ErrorResponse, 19 | data: Option>, 20 | } 21 | 22 | /// Localization item 23 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 24 | pub struct Item { 25 | /// Item ID 26 | #[serde(rename = "_id")] 27 | pub id: String, 28 | /// Item name 29 | #[serde(rename = "_name")] 30 | pub name: String, 31 | /// Item parent ID 32 | #[serde(rename = "_parent")] 33 | pub parent: String, 34 | /// Item type 35 | #[serde(rename = "_type")] 36 | pub item_type: String, 37 | /// Item properties 38 | #[serde(rename = "_props")] 39 | pub props: Props, 40 | /// ? 41 | #[serde(rename = "_proto")] 42 | pub proto: Option, 43 | } 44 | 45 | /// All item properties. 46 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 47 | #[serde(rename_all = "PascalCase")] 48 | pub struct Props { 49 | /// Full item name 50 | pub name: Option, 51 | /// Short item name 52 | pub short_name: Option, 53 | /// Item description 54 | pub description: Option, 55 | /// Item weight 56 | pub weight: Option, 57 | /// Item background color 58 | pub background_color: Option, 59 | /// Item width 60 | pub width: Option, 61 | /// Item height 62 | pub height: Option, 63 | /// Item maximum stack size 64 | pub stack_max_size: Option, 65 | /// Item rarity 66 | pub rarity: Option, 67 | /// Item spawn chance 68 | pub spawn_chance: Option, 69 | /// Item price? 70 | pub credits_price: Option, 71 | /// Item sound 72 | pub item_sound: Option, 73 | /// Item prefab 74 | pub prefab: Option, 75 | /// Item prefab? 76 | pub use_prefab: Option, 77 | /// Item stack count 78 | pub stack_objects_count: Option, 79 | /// ? 80 | pub not_shown_in_slot: Option, 81 | /// Item is examined by default. 82 | pub examined_by_default: Option, 83 | /// Time it takes to examine an item in seconds. 84 | pub examine_time: Option, 85 | /// Item cannot be deleted. 86 | pub is_undiscardable: Option, 87 | /// Item cannot be sold. 88 | pub is_unsaleable: Option, 89 | /// Item cannot be bought. 90 | pub is_unbuyable: Option, 91 | /// Item cannot be given?. 92 | pub is_ungivable: Option, 93 | /// Item locked after equipping?. 94 | #[serde(rename = "IsLockedafterEquip")] 95 | pub is_locked_after_equip: Option, 96 | /// Item is needed for quests. 97 | pub quest_item: Option, 98 | /// Experience for looting item. 99 | pub loot_experience: Option, 100 | /// Experience for examining item. 101 | pub examine_experience: Option, 102 | /// ? 103 | pub hide_entrails: Option, 104 | /// Item repair cost 105 | pub repair_cost: Option, 106 | /// Item repair speed 107 | pub repair_speed: Option, 108 | /// ? 109 | pub extra_size_left: Option, 110 | /// ? 111 | pub extra_size_right: Option, 112 | /// ? 113 | pub extra_size_up: Option, 114 | /// ? 115 | pub extra_size_down: Option, 116 | /// ? 117 | pub extra_size_force_add: Option, 118 | /// ? 119 | pub merges_with_children: Option, 120 | /// Item can be sold on the flea market. 121 | pub can_sell_on_ragfair: Option, 122 | /// Item can be traded on the flea market. 123 | pub can_require_on_ragfair: Option, 124 | /// Item banned from the flea market. 125 | pub banned_from_ragfair: Option, 126 | /// ? 127 | pub conflicting_items: Option>, 128 | /// Item fixed price 129 | pub fixed_price: Option, 130 | /// Item cannot be looted. 131 | pub unlootable: Option, 132 | /// Item cannot be looted from slot. 133 | pub unlootable_from_slot: Option, 134 | /// Item cannot be looted from side. 135 | pub unlootable_from_side: Option>, 136 | /// ? 137 | #[serde(rename = "ChangePriceCoef")] 138 | pub change_price_coefficient: Option, 139 | /// Item spawns locations 140 | pub allow_spawn_on_locations: Option>, 141 | /// ? 142 | pub send_to_client: Option, 143 | /// ? 144 | pub animation_variants_number: Option, 145 | /// ? 146 | pub discarding_block: Option, 147 | /// ? 148 | pub max_resource: Option, 149 | /// ? 150 | pub resource: Option, 151 | /// ? 152 | pub dog_tag_qualities: Option, 153 | /// Item grids 154 | pub grids: Option>, 155 | /// Item slots 156 | pub slots: Option>, 157 | /// Items can be equipped during a raid. 158 | pub can_put_into_during_the_raid: Option, 159 | /// Item cannot be removed from slots during a raid. 160 | pub cant_remove_from_slots_during_raid: Option>, 161 | /// Item key IDs 162 | pub key_ids: Option>, 163 | /// Item tag color 164 | pub tag_color: Option, 165 | /// Item tag name 166 | pub tag_name: Option, 167 | /// Item durability 168 | pub durability: Option, 169 | /// Weapon accuracy 170 | pub accuracy: Option, 171 | /// Weapon recoil 172 | pub recoil: Option, 173 | /// Weapon loudness 174 | pub loudness: Option, 175 | /// Weapon effective distance 176 | pub effective_distance: Option, 177 | /// Item ergonomics? 178 | pub ergonomics: Option, 179 | /// Item velocity 180 | pub velocity: Option, 181 | /// ? 182 | pub raid_moddable: Option, 183 | /// ? 184 | pub tool_moddable: Option, 185 | /// ? 186 | pub blocks_folding: Option, 187 | /// ? 188 | pub blocks_collapsible: Option, 189 | /// ? 190 | pub is_animated: Option, 191 | /// Weapon has a buttstock. 192 | pub has_shoulder_contact: Option, 193 | /// Weapon sighting range 194 | pub sighting_range: Option, 195 | /// Weapon firing modes 196 | pub modes_count: Option, 197 | /// Weapon muzzle mod type 198 | #[serde(rename = "muzzleModType")] 199 | pub muzzle_mod_type: Option, 200 | /// Weapon sight mod type 201 | #[serde(rename = "sightModType")] 202 | pub sight_mod_type: Option, 203 | /// Weapon has a telescopic sight equipped. 204 | #[serde(rename = "variableZoom")] 205 | pub variable_zoom: Option, 206 | /// Weapon telescopic sight magnification levels. 207 | #[serde(rename = "varZoomCount")] 208 | pub var_zoom_count: Option, 209 | /// Weapon telescopic sight magnification? 210 | #[serde(rename = "varZoomAdd")] 211 | pub var_zoom_add: Option, 212 | /// Weapon aiming sensitivity 213 | #[serde(rename = "aimingSensitivity")] 214 | pub aiming_sensitivity: Option, 215 | /// Weapon sight mode count 216 | pub sight_modes_count: Option, 217 | /// Weapon sight calibration distances 218 | pub optic_calibration_distances: Option>, 219 | /// ? 220 | pub intensity: Option, 221 | /// ? 222 | pub mask: Option, 223 | /// ? 224 | pub mask_size: Option, 225 | /// Item noise intensity 226 | pub noise_intensity: Option, 227 | /// Item noise scale 228 | pub noise_scale: Option, 229 | /// Item color 230 | pub color: Option, 231 | /// ? 232 | pub diffuse_intensity: Option, 233 | /// ? 234 | pub has_hinge: Option, 235 | /// ? 236 | pub ramp_palette: Option, 237 | /// ? 238 | pub depth_fade: Option, 239 | /// ? 240 | #[serde(rename = "RoughnessCoef")] 241 | pub roughness_coefficient: Option, 242 | /// ? 243 | #[serde(rename = "SpecularCoef")] 244 | pub specular_coefficient: Option, 245 | /// ? 246 | #[serde(rename = "MainTexColorCoef")] 247 | pub main_tex_color_coefficient: Option, 248 | /// ? 249 | pub minimum_temperature_value: Option, 250 | /// ? 251 | pub ramp_shift: Option, 252 | /// ? 253 | pub heat_min: Option, 254 | /// ? 255 | pub cold_max: Option, 256 | /// ? 257 | pub is_noisy: Option, 258 | /// ? 259 | pub is_fps_stuck: Option, 260 | /// ? 261 | pub is_glitch: Option, 262 | /// ? 263 | pub is_motion_blurred: Option, 264 | /// ? 265 | pub is_pixelated: Option, 266 | /// ? 267 | pub pixelation_block_count: Option, 268 | /// ? 269 | #[serde(rename = "magAnimationIndex")] 270 | pub mag_animation_index: Option, 271 | /// Weapon cartridge/ammo 272 | pub cartridges: Option>, 273 | /// ? 274 | pub can_fast: Option, 275 | /// ? 276 | pub can_hit: Option, 277 | /// ? 278 | pub can_admin: Option, 279 | /// ? 280 | pub load_unload_modifier: Option, 281 | /// ? 282 | pub check_time_modifier: Option, 283 | /// ? 284 | pub check_override: Option, 285 | /// ? 286 | pub reload_mag_type: Option, 287 | /// ? 288 | pub visible_ammo_ranges_string: Option, 289 | /// Weapon has a buttstock. 290 | pub is_shoulder_contact: Option, 291 | /// Weapon stock is foldable. 292 | pub foldable: Option, 293 | /// Weapon stock is retractable? 294 | pub retractable: Option, 295 | /// ? 296 | pub size_reduce_right: Option, 297 | /// ? 298 | pub center_of_impact: Option, 299 | /// Shotgun shot dispersion 300 | pub shotgun_dispersion: Option, 301 | /// Weapon has a suppressor. 302 | pub is_silencer: Option, 303 | /// Item search sound 304 | pub search_sound: Option, 305 | /// ? 306 | pub blocks_armor_vest: Option, 307 | /// ? 308 | #[serde(rename = "speedPenaltyPercent")] 309 | pub speed_penalty_percent: Option, 310 | /// ? 311 | pub grid_layout_name: Option, 312 | /// ? 313 | pub spawn_filter: Option>, 314 | /// Unknown type 315 | #[serde(rename = "containType")] 316 | pub contain_type: Option, 317 | /// Item width in inventory. 318 | #[serde(rename = "sizeWidth")] 319 | pub size_width: Option, 320 | /// Item height in inventory. 321 | #[serde(rename = "sizeHeight")] 322 | pub size_height: Option, 323 | /// ? 324 | #[serde(rename = "isSecured")] 325 | pub is_secured: Option, 326 | /// ? 327 | #[serde(rename = "spawnTypes")] 328 | pub spawn_types: Option, 329 | /// Unknown type 330 | #[serde(rename = "lootFilter")] 331 | pub loot_filter: Option, 332 | /// Item spawn rarity 333 | #[serde(rename = "spawnRarity")] 334 | pub spawn_rarity: Option, 335 | /// ? 336 | #[serde(rename = "minCountSpawn")] 337 | pub min_count_spawn: Option, 338 | /// ? 339 | #[serde(rename = "maxCountSpawn")] 340 | pub max_count_spawn: Option, 341 | /// Unknown type 342 | #[serde(rename = "openedByKeyID")] 343 | pub opened_by_key_id: Option, 344 | /// Item rig layout name 345 | pub rig_layout_name: Option, 346 | /// Item maximum durability 347 | pub max_durability: Option, 348 | /// Item armor zone 349 | #[serde(rename = "armorZone")] 350 | pub armor_zone: Option>, 351 | /// Item armor class 352 | #[serde( 353 | default, 354 | rename = "armorClass", 355 | deserialize_with = "deserialize_integer_to_option_string" 356 | )] 357 | pub armor_class: Option, 358 | /// ? 359 | #[serde(rename = "mousePenalty")] 360 | pub mouse_penalty: Option, 361 | /// ? 362 | #[serde(rename = "weaponErgonomicPenalty")] 363 | pub weapon_ergonomic_penalty: Option, 364 | /// ? 365 | pub blunt_throughput: Option, 366 | /// Item armor material 367 | pub armor_material: Option, 368 | /// Weapon class 369 | #[serde(rename = "weapClass")] 370 | pub weapon_class: Option, 371 | /// Weapon type 372 | #[serde(rename = "weapUseType")] 373 | pub weapon_use_type: Option, 374 | /// Weapon ammo caliber 375 | pub ammo_caliber: Option, 376 | /// ? 377 | pub operating_resource: Option, 378 | /// ? 379 | pub repair_complexity: Option, 380 | /// Item spawn minimum durability chance 381 | #[serde(rename = "durabSpawnMin")] 382 | pub durability_spawn_min: Option, 383 | /// Item spawn maximum durability chance 384 | #[serde(rename = "durabSpawnMax")] 385 | pub durability_spawn_max: Option, 386 | /// Weapon fast reload 387 | #[serde(rename = "isFastReload")] 388 | pub is_fast_reload: Option, 389 | /// Weapon recoil vertical force 390 | pub recoil_force_up: Option, 391 | /// Weapon recoil back force 392 | pub recoil_force_back: Option, 393 | /// ? 394 | pub convergence: Option, 395 | /// Weapon recoil angle 396 | pub recoil_angle: Option, 397 | /// Weapon fire modes 398 | #[serde(rename = "weapFireType")] 399 | pub weapon_fire_type: Option>, 400 | /// Weapon recoil dispersion rate 401 | #[serde(rename = "RecolDispersion")] 402 | pub recoil_dispersion: Option, 403 | /// Weapon fire mode 404 | #[serde(rename = "bFirerate")] 405 | pub firerate: Option, 406 | /// Weapon effective distance? 407 | #[serde(rename = "bEffDist")] 408 | pub eff_dist: Option, 409 | /// Weapon maximum sound distance 410 | #[serde(rename = "bHearDist")] 411 | pub hear_dist: Option, 412 | /// Weapon has a round in the chamber. 413 | #[serde(rename = "isChamberLoad")] 414 | pub is_chamber_load: Option, 415 | /// ? 416 | #[serde(rename = "chamberAmmoCount")] 417 | pub chamber_ammo_count: Option, 418 | /// Weapon bolt catch is engaged. 419 | #[serde(rename = "isBoltCatch")] 420 | pub is_bolt_catch: Option, 421 | /// Weapon magazine type?. 422 | #[serde(rename = "defMagType")] 423 | pub def_mag_type: Option, 424 | /// Weapon ammo type?. 425 | #[serde(rename = "defAmmo")] 426 | pub def_ammo: Option, 427 | /// Weapon chamber?. 428 | pub chambers: Option>, 429 | /// ? 430 | pub camera_recoil: Option, 431 | /// ? 432 | pub camera_snap: Option, 433 | /// Weapon reload mode 434 | pub reload_mode: Option, 435 | /// ? 436 | pub aim_plane: Option, 437 | /// ? 438 | pub deviation_curve: Option, 439 | /// ? 440 | pub deviation_max: Option, 441 | /// ? 442 | #[serde(rename = "TacticalReloadStiffnes")] 443 | pub tactical_reload_stiffness: Option, 444 | /// ? 445 | pub tactical_reload_fixation: Option, 446 | /// ? 447 | pub recoil_center: Option, 448 | /// ? 449 | pub rotation_center: Option, 450 | /// ? 451 | pub rotation_center_no_stock: Option, 452 | /// ? 453 | pub folded_slot: Option, 454 | /// ? 455 | pub compact_handling: Option, 456 | /// Item minimum repair degradation 457 | pub min_repair_degradation: Option, 458 | /// Item maximum repair degradation 459 | pub max_repair_degradation: Option, 460 | /// Weapon iron sight zero 461 | pub iron_sight_range: Option, 462 | /// Weapon's bolt catch must be engaged for external reload (pressing R). 463 | #[serde(rename = "MustBoltBeOpennedForExternalReload")] 464 | pub must_bolt_be_opened_for_external_reload: Option, 465 | /// Weapon's bolt catch must be engaged for internal reload (reload inside inventory). 466 | #[serde(rename = "MustBoltBeOpennedForInternalReload")] 467 | pub must_bolt_be_opened_for_internal_reload: Option, 468 | /// Weapon is bolt action operated. 469 | pub bolt_action: Option, 470 | /// ? 471 | pub hip_accuracy_restoration_delay: Option, 472 | /// ? 473 | pub hip_accuracy_restoration_speed: Option, 474 | /// ? 475 | #[serde(rename = "HipInnaccuracyGain")] 476 | pub hip_inaccuracy_gain: Option, 477 | /// ? 478 | pub manual_bolt_catch: Option, 479 | /// Item blocks earpiece. 480 | pub blocks_earpiece: Option, 481 | /// Item blocks eye wear. 482 | #[serde(rename = "BlocksEyewear")] 483 | pub blocks_eye_wear: Option, 484 | /// Item blocks head wear. 485 | #[serde(rename = "BlocksHeadwear")] 486 | pub blocks_head_wear: Option, 487 | /// Item blocks face cover. 488 | pub blocks_face_cover: Option, 489 | /// Time it takes to consume food. 490 | #[serde(rename = "foodUseTime")] 491 | pub food_use_time: Option, 492 | /// Food effect type. 493 | #[serde(rename = "foodEffectType")] 494 | pub food_effect_type: Option, 495 | /// ? 496 | pub stimulator_buffs: Option, 497 | /// Health effects on player 498 | #[serde(rename = "effects_health")] 499 | pub effects_health: Option, 500 | /// Damage effects on player 501 | #[serde(rename = "effects_damage")] 502 | pub effects_damage: Option, 503 | /// Speed effects on player 504 | #[serde(rename = "effects_speed")] 505 | pub effects_speed: Option, 506 | /// Maximum item usage 507 | pub maximum_number_of_usage: Option, 508 | /// Knife hit delay 509 | #[serde(rename = "knifeHitDelay")] 510 | pub knife_hit_delay: Option, 511 | /// Knife slash rate 512 | #[serde(rename = "knifeHitSlashRate")] 513 | pub knife_hit_slash_rate: Option, 514 | /// Knife stab rate 515 | #[serde(rename = "knifeHitStabRate")] 516 | pub knife_hit_stab_rate: Option, 517 | /// Knife effective hit radius 518 | #[serde(rename = "knifeHitRadius")] 519 | pub knife_hit_radius: Option, 520 | /// Knife slash damage 521 | #[serde(rename = "knifeHitSlashDam")] 522 | pub knife_hit_slash_damage: Option, 523 | /// Knife stab damage 524 | #[serde(rename = "knifeHitStabDam")] 525 | pub knife_hit_stab_damage: Option, 526 | /// Knife durability 527 | #[serde(rename = "knifeDurab")] 528 | pub knife_durability: Option, 529 | /// ? 530 | pub primary_distance: Option, 531 | /// ? 532 | #[serde(rename = "SecondryDistance")] 533 | pub secondary_distance: Option, 534 | /// ? 535 | pub slash_penetration: Option, 536 | /// ? 537 | pub stab_penetration: Option, 538 | /// ? 539 | pub primary_consumption: Option, 540 | /// ? 541 | #[serde(rename = "SecondryConsumption")] 542 | pub secondary_consumption: Option, 543 | /// ? 544 | pub deflection_consumption: Option, 545 | /// ? 546 | pub config_path_str: Option, 547 | /// ? 548 | pub max_markers_count: Option, 549 | /// ? 550 | #[serde(rename = "scaleMin")] 551 | pub scale_min: Option, 552 | /// ? 553 | #[serde(rename = "scaleMax")] 554 | pub scale_max: Option, 555 | /// Time it takes to consume medkit. 556 | #[serde(rename = "medUseTime")] 557 | pub med_use_time: Option, 558 | /// Medkit effect type 559 | #[serde(rename = "medEffectType")] 560 | pub med_effect_type: Option, 561 | /// ? 562 | pub max_hp_resource: Option, 563 | /// ? 564 | #[serde(rename = "hpResourceRate")] 565 | pub hp_resource_rate: Option, 566 | /// ? 567 | pub max_efficiency: Option, 568 | /// ? 569 | pub addiction: Option, 570 | /// ? 571 | pub overdose: Option, 572 | /// ? 573 | pub overdose_recovery: Option, 574 | /// ? 575 | pub addiction_recovery: Option, 576 | /// Unknown type 577 | pub buffs: Option, 578 | /// ? 579 | #[serde(rename = "apResource")] 580 | pub ap_resource: Option, 581 | /// ? 582 | #[serde(rename = "krResource")] 583 | pub kr_resource: Option, 584 | /// ? 585 | pub stack_min_random: Option, 586 | /// ? 587 | pub stack_max_random: Option, 588 | /// Ammo type 589 | #[serde(rename = "ammoType")] 590 | pub ammo_type: Option, 591 | /// Ammo damage 592 | pub damage: Option, 593 | /// Ammo accuracy 594 | #[serde(rename = "ammoAccr")] 595 | pub ammo_accr: Option, 596 | /// Ammo recoil 597 | #[serde(rename = "ammoRec")] 598 | pub ammo_rec: Option, 599 | /// Ammo effective distance 600 | #[serde(rename = "ammoDist")] 601 | pub ammo_dist: Option, 602 | /// Buckshot bullet count? 603 | #[serde(rename = "buckshotBullets")] 604 | pub buckshot_bullets: Option, 605 | /// Ammo penetration power 606 | pub penetration_power: Option, 607 | /// Ammo ? 608 | #[serde(rename = "penetration_power_diviation")] 609 | pub penetration_power_deviation: Option, 610 | /// Ammo ? 611 | #[serde(rename = "ammoHear")] 612 | pub ammo_hear: Option, 613 | /// Ammo sound effect 614 | #[serde(rename = "ammoSfx")] 615 | pub ammo_sfx: Option, 616 | /// Item chance of misfire 617 | pub misfire_chance: Option, 618 | /// ? 619 | pub min_fragments_count: Option, 620 | /// ? 621 | pub max_fragments_count: Option, 622 | /// ? 623 | #[serde(rename = "ammoShiftChance")] 624 | pub ammo_shift_chance: Option, 625 | /// Ammo casing name 626 | #[serde(rename = "casingName")] 627 | pub casing_name: Option, 628 | /// Ammo casing ejection power 629 | #[serde(rename = "casingEjectPower")] 630 | pub casing_eject_power: Option, 631 | /// Ammo casing mass 632 | #[serde(rename = "casingMass")] 633 | pub casing_mass: Option, 634 | /// Ammo casing sound 635 | #[serde(rename = "casingSounds")] 636 | pub casing_sounds: Option, 637 | /// Ammo projectile count 638 | pub projectile_count: Option, 639 | /// Ammo initial speed 640 | pub initial_speed: Option, 641 | /// Ammo penetration chance 642 | pub penetration_chance: Option, 643 | /// Ammo ricochet chance 644 | pub ricochet_chance: Option, 645 | /// Ammo fragmentation chance 646 | pub fragmentation_chance: Option, 647 | /// Ammo ballistic coefficient 648 | #[serde(rename = "BallisticCoeficient")] 649 | pub ballistic_coefficient: Option, 650 | /// ? 651 | pub deterioration: Option, 652 | /// ? 653 | pub speed_retardation: Option, 654 | /// Ammo is a tracer round 655 | pub tracer: Option, 656 | /// Tracer color 657 | pub tracer_color: Option, 658 | /// Tracer distance 659 | pub tracer_distance: Option, 660 | /// Ammo armor damage 661 | pub armor_damage: Option, 662 | /// Ammo caliber 663 | pub caliber: Option, 664 | /// ? 665 | pub stamina_burn_per_damage: Option, 666 | /// ? 667 | pub show_bullet: Option, 668 | /// ? 669 | pub has_grenader_component: Option, 670 | /// ? 671 | pub fuze_arm_time_sec: Option, 672 | /// Item explosion strength 673 | pub explosion_strength: Option, 674 | /// Item minimum explosion distance 675 | pub min_explosion_distance: Option, 676 | /// Item maximum explosion distance 677 | pub max_explosion_distance: Option, 678 | /// Explosion fragment count 679 | pub fragments_count: Option, 680 | /// Explosion fragment type 681 | pub fragment_type: Option, 682 | /// ? 683 | pub show_hit_effect_on_explode: Option, 684 | /// Explosion type 685 | pub explosion_type: Option, 686 | /// ? 687 | pub ammo_life_time_sec: Option, 688 | /// ? 689 | pub stack_slots: Option>, 690 | /// Item type 691 | #[serde(rename = "type")] 692 | pub item_type: Option, 693 | /// ? 694 | #[serde(rename = "eqMin")] 695 | pub eq_min: Option, 696 | /// ? 697 | #[serde(rename = "eqMax")] 698 | pub eq_max: Option, 699 | /// ? 700 | #[serde(rename = "rate")] 701 | pub rate: Option, 702 | /// ? 703 | pub throw_type: Option, 704 | /// ? 705 | pub strength: Option, 706 | /// ? 707 | pub contusion_distance: Option, 708 | /// ? 709 | #[serde(rename = "throwDamMax")] 710 | pub throw_dam_max: Option, 711 | /// ? 712 | pub expl_delay: Option, 713 | /// ? 714 | pub blindness: Option, 715 | /// ? 716 | pub contusion: Option, 717 | /// ? 718 | pub emit_time: Option, 719 | /// ? 720 | pub can_be_hidden_during_throw: Option, 721 | /// ? 722 | pub indestructibility: Option, 723 | /// ? 724 | #[serde(rename = "headSegments")] 725 | pub head_segments: Option>, 726 | /// ? 727 | pub face_shield_component: Option, 728 | /// ? 729 | pub face_shield_mask: Option, 730 | /// ? 731 | pub material_type: Option, 732 | /// ? 733 | pub ricochet_params: Option, 734 | /// ? 735 | pub deaf_strength: Option, 736 | /// ? 737 | pub distortion: Option, 738 | /// ? 739 | #[serde(rename = "CompressorTreshold")] 740 | pub compressor_threshold: Option, 741 | /// ? 742 | pub compressor_attack: Option, 743 | /// ? 744 | pub compressor_release: Option, 745 | /// ? 746 | pub compressor_gain: Option, 747 | /// ? 748 | pub cutoff_freq: Option, 749 | /// ? 750 | pub resonance: Option, 751 | /// ? 752 | pub compressor_volume: Option, 753 | /// Item ambient volume 754 | pub ambient_volume: Option, 755 | /// Item dry volume 756 | pub dry_volume: Option, 757 | } 758 | 759 | /// Item prefab 760 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 761 | pub struct Prefab { 762 | /// Prefab path 763 | pub path: String, 764 | /// ? 765 | pub rcid: String, 766 | } 767 | 768 | /// Item grid 769 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 770 | pub struct Grid { 771 | /// Grid ID 772 | #[serde(rename = "_id")] 773 | pub id: String, 774 | /// Grid name 775 | #[serde(rename = "_name")] 776 | pub name: String, 777 | /// Grid parent ID 778 | #[serde(rename = "_parent")] 779 | pub parent: String, 780 | /// Grid properties 781 | #[serde(rename = "_props")] 782 | pub props: GridProps, 783 | /// ? 784 | #[serde(rename = "_proto")] 785 | pub proto: String, 786 | } 787 | 788 | /// Item grid properties 789 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 790 | #[serde(rename_all = "camelCase")] 791 | pub struct GridProps { 792 | /// Grid filters 793 | pub filters: Vec, 794 | /// Grid height 795 | pub cells_h: u64, 796 | /// Grid width? 797 | #[serde(rename = "cellsV")] 798 | pub cells_w: u64, 799 | /// Minimum grid space 800 | pub min_count: u64, 801 | /// Maximum grid space 802 | pub max_count: u64, 803 | /// Grid maximum weight 804 | pub max_weight: u64, 805 | } 806 | 807 | /// Item grid filter 808 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 809 | #[serde(rename_all = "PascalCase")] 810 | pub struct GridFilter { 811 | /// Grid filters 812 | pub filter: Vec, 813 | /// Grid exclusion filter 814 | pub excluded_filter: Vec, 815 | } 816 | 817 | /// Item slot 818 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 819 | pub struct Slot { 820 | /// Slot ID 821 | #[serde(rename = "_id")] 822 | pub id: String, 823 | /// Slot name 824 | #[serde(rename = "_name")] 825 | pub name: String, 826 | /// Slot parent ID 827 | #[serde(rename = "_parent")] 828 | pub parent: String, 829 | /// Slot properties 830 | #[serde(rename = "_props")] 831 | pub props: SlotProps, 832 | /// Slot is required 833 | #[serde(rename = "_required")] 834 | pub required: bool, 835 | /// Merge slot with children 836 | #[serde(rename = "_mergeSlotWithChildren")] 837 | pub merge_slot_with_children: bool, 838 | /// ? 839 | #[serde(rename = "_proto")] 840 | pub proto: String, 841 | } 842 | 843 | /// Item slot properties 844 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 845 | #[serde(rename_all = "PascalCase")] 846 | pub struct SlotProps { 847 | /// ? 848 | pub slot: Option, 849 | /// Slot animation 850 | pub animation_index: Option, 851 | /// Slot filters 852 | pub filters: Option>, 853 | } 854 | 855 | /// Item slot filter 856 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 857 | #[serde(rename_all = "PascalCase")] 858 | pub struct SlotFilter { 859 | /// ? 860 | pub slot: Option, 861 | /// Slot filter animation index 862 | pub animation_index: Option, 863 | /// Slot filter filters 864 | pub filters: Vec, 865 | } 866 | 867 | /// Item cartridge/ammo 868 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 869 | pub struct Cartridge { 870 | /// Cartridge ID 871 | #[serde(rename = "_id")] 872 | pub id: String, 873 | /// Cartridge name 874 | #[serde(rename = "_name")] 875 | pub name: String, 876 | /// Cartridge parent ID 877 | #[serde(rename = "_parent")] 878 | pub parent: String, 879 | /// Maximum number of cartridges 880 | #[serde(rename = "_max_count")] 881 | pub max_count: u64, 882 | /// Cartridge properties 883 | #[serde(rename = "_props")] 884 | pub props: CartridgeProps, 885 | /// ? 886 | #[serde(rename = "_proto")] 887 | pub proto: String, 888 | } 889 | 890 | /// Cartridge properties 891 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 892 | pub struct CartridgeProps { 893 | /// Cartridge filters 894 | pub filters: Vec, 895 | } 896 | 897 | /// Cartridge filter 898 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 899 | #[serde(rename_all = "PascalCase")] 900 | pub struct CartridgeFilter { 901 | /// Cartridge filter filters? 902 | pub filter: Vec, 903 | } 904 | 905 | /// Item armor zone 906 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 907 | pub enum ArmorZone { 908 | /// Head 909 | Head, 910 | /// Chest 911 | Chest, 912 | /// Stomach 913 | Stomach, 914 | /// Left arm 915 | LeftArm, 916 | /// Right arm 917 | RightArm, 918 | /// Left leg 919 | LeftLeg, 920 | /// Right leg 921 | RightLeg, 922 | } 923 | 924 | /// Item fire mode 925 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 926 | #[serde(rename_all = "lowercase")] 927 | pub enum FireMode { 928 | /// Single fire 929 | Single, 930 | /// Burst mode 931 | Burst, 932 | /// Full auto 933 | FullAuto, 934 | } 935 | 936 | /// Item chamber 937 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 938 | pub struct Chamber { 939 | /// Chamber ID 940 | #[serde(rename = "_id")] 941 | pub id: String, 942 | /// Chamber name 943 | #[serde(rename = "_name")] 944 | pub name: String, 945 | /// Chamber parent ID 946 | #[serde(rename = "_parent")] 947 | pub parent: String, 948 | /// Chamber properties 949 | #[serde(rename = "_props")] 950 | pub props: ChamberProps, 951 | /// Chamber is required 952 | #[serde(rename = "_required")] 953 | pub required: bool, 954 | /// Merge chamber slot with children 955 | #[serde(rename = "_mergeSlotWithChildren")] 956 | pub merge_slot_with_children: bool, 957 | /// ? 958 | #[serde(rename = "_proto")] 959 | pub proto: String, 960 | } 961 | 962 | /// Chamber properties 963 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 964 | pub struct ChamberProps { 965 | /// Chamber filters 966 | pub filters: Vec, 967 | } 968 | 969 | /// Chamber filter 970 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 971 | #[serde(rename_all = "PascalCase")] 972 | pub struct ChamberFilter { 973 | /// Chamber filter filters? 974 | pub filter: Vec, 975 | } 976 | 977 | /// RGB color 978 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 979 | pub struct Color { 980 | /// Red 981 | pub r: u8, 982 | /// Green 983 | pub g: u8, 984 | /// Blue 985 | pub b: u8, 986 | /// Alpha 987 | pub a: u8, 988 | } 989 | 990 | /// 3D Coordinate 991 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 992 | pub struct Coordinate { 993 | /// x plane 994 | pub x: f64, 995 | /// y plane 996 | pub y: f64, 997 | /// z plane 998 | pub z: f64, 999 | } 1000 | 1001 | /// Head parts 1002 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1003 | pub enum HeadSegment { 1004 | /// Top of head 1005 | Top, 1006 | /// Nape 1007 | Nape, 1008 | /// Ears 1009 | Ears, 1010 | /// Eyes 1011 | Eyes, 1012 | /// Jaws 1013 | Jaws, 1014 | } 1015 | 1016 | /// Health effects on player 1017 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1018 | pub struct HealthEffects { 1019 | /// Body effects 1020 | pub common: Health, 1021 | /// Head effects 1022 | pub head: Health, 1023 | /// Left arm effects 1024 | pub arm_left: Health, 1025 | /// Right arm effects 1026 | pub arm_right: Health, 1027 | /// Chest effects 1028 | pub chest: Health, 1029 | /// Stomach effects 1030 | pub tummy: Health, 1031 | /// Left leg effects 1032 | pub leg_left: Health, 1033 | /// Right leg effects 1034 | pub leg_right: Health, 1035 | /// Energy effects 1036 | pub energy: Health, 1037 | /// Hydration effects 1038 | pub hydration: Health, 1039 | } 1040 | 1041 | /// Health effect 1042 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1043 | pub struct Health { 1044 | /// Effect value 1045 | pub value: i64, 1046 | /// Effect percent 1047 | pub percent: bool, 1048 | /// ? 1049 | pub time: u64, 1050 | /// Effect duration 1051 | pub duration: u64, 1052 | } 1053 | 1054 | /// Damage effects on player 1055 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1056 | #[serde(rename_all = "camelCase")] 1057 | pub struct DamageEffects { 1058 | /// Bloodloss effect 1059 | pub bloodloss: Damage, 1060 | /// Fracture effect 1061 | pub fracture: Damage, 1062 | /// Pain effect 1063 | pub pain: Damage, 1064 | /// Contusion effect 1065 | pub contusion: Damage, 1066 | /// Toxication effect 1067 | pub toxication: Damage, 1068 | /// Radiation exposure 1069 | #[serde(rename = "radExposure")] 1070 | pub radiation_exposure: Damage, 1071 | } 1072 | 1073 | /// Damage effect 1074 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1075 | #[serde(rename_all = "camelCase")] 1076 | pub struct Damage { 1077 | /// ? 1078 | pub remove: bool, 1079 | /// ? 1080 | pub time: u64, 1081 | /// Damage effect duration 1082 | pub duration: u64, 1083 | /// ? 1084 | pub fade_out: Option, 1085 | /// ? 1086 | pub cost: Option, 1087 | /// Damage minimum health penalty 1088 | pub health_penalty_min: Option, 1089 | /// Damage maximum health penalty 1090 | pub health_penalty_max: Option, 1091 | } 1092 | 1093 | /// Speed effects on player 1094 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1095 | #[serde(rename_all = "camelCase")] 1096 | pub struct SpeedEffects { 1097 | /// Mobility effect 1098 | pub mobility: Speed, 1099 | /// Recoil effect 1100 | pub recoil: Speed, 1101 | /// Reload effect 1102 | pub reload_speed: Speed, 1103 | /// Loot effect 1104 | pub loot_speed: Speed, 1105 | /// Unlock effect 1106 | pub unlock_speed: Speed, 1107 | } 1108 | 1109 | /// Speed effect 1110 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1111 | pub struct Speed { 1112 | /// Effect value 1113 | pub value: i64, 1114 | /// Effect percent 1115 | pub percent: bool, 1116 | /// ? 1117 | pub time: u64, 1118 | /// Effect duration 1119 | pub duration: u64, 1120 | } 1121 | 1122 | /// ? 1123 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1124 | pub struct StackSlot { 1125 | /// Stack slot ID 1126 | #[serde(rename = "_id")] 1127 | pub id: String, 1128 | /// Stack slot name 1129 | #[serde(rename = "_name")] 1130 | pub name: String, 1131 | /// Stack slot parent ID 1132 | #[serde(rename = "_parent")] 1133 | pub parent: String, 1134 | /// Maximum stack slots 1135 | #[serde(rename = "_max_count")] 1136 | pub max_count: u64, 1137 | /// Stack slot properties 1138 | #[serde(rename = "_props")] 1139 | pub props: StackSlotProps, 1140 | /// ? 1141 | #[serde(rename = "_proto")] 1142 | pub proto: String, 1143 | } 1144 | 1145 | /// Stack slot properties 1146 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1147 | pub struct StackSlotProps { 1148 | /// Stack slot filters 1149 | pub filters: Vec, 1150 | } 1151 | 1152 | /// Stack slot filter 1153 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1154 | #[serde(rename_all = "PascalCase")] 1155 | pub struct StackSlotFilter { 1156 | /// Stack slot filter filters? 1157 | pub filter: Vec, 1158 | } 1159 | 1160 | #[derive(Debug, Deserialize)] 1161 | struct LocationsResponse { 1162 | #[serde(flatten)] 1163 | error: ErrorResponse, 1164 | data: Option, 1165 | } 1166 | 1167 | /// All in-game locations 1168 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1169 | pub struct Locations { 1170 | /// Locations 1171 | pub locations: HashMap, 1172 | /// Location paths 1173 | pub paths: Vec, 1174 | } 1175 | 1176 | /// In-game location 1177 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1178 | #[serde(rename_all = "PascalCase")] 1179 | pub struct Location { 1180 | /// Location is enabled 1181 | pub enabled: bool, 1182 | /// Location is locked 1183 | pub locked: bool, 1184 | /// Insurance is available 1185 | pub insurance: bool, 1186 | /// ? 1187 | pub safe_location: bool, 1188 | /// Location name 1189 | pub name: String, 1190 | /// Location description 1191 | pub description: String, 1192 | /// Location scene prefab 1193 | pub scene: Scene, 1194 | /// Location area 1195 | pub area: f64, 1196 | /// Required level for location access 1197 | pub required_player_level: u64, 1198 | /// ? 1199 | #[serde(rename = "surv_gather_minutes")] 1200 | pub surv_gather_minutes: u64, 1201 | /// Minimum players on location 1202 | pub min_players: u64, 1203 | /// Maximum players on location 1204 | pub max_players: u64, 1205 | /// ? 1206 | #[serde(rename = "sav_gather_minutes")] 1207 | pub scav_gather_minutes: u64, 1208 | /// Number of extraction points 1209 | #[serde(rename = "exit_count")] 1210 | pub exit_count: u64, 1211 | /// ? 1212 | #[serde(rename = "exit_access_time")] 1213 | pub exit_access_time: u64, 1214 | /// ? 1215 | #[serde(rename = "exit_time")] 1216 | pub exit_time: u64, 1217 | /// Location preview 1218 | pub preview: Preview, 1219 | /// Location icon X 1220 | pub icon_x: u64, 1221 | /// Location icon Y 1222 | pub icon_y: u64, 1223 | /// Unknown type 1224 | #[serde(rename = "filter_ex")] 1225 | pub filter_ex: Vec, 1226 | /// NPC waves on location 1227 | #[serde(rename = "waves")] 1228 | pub waves: Vec, 1229 | /// Unknown type 1230 | #[serde(rename = "limits")] 1231 | pub limits: Vec, 1232 | /// Average play time on location 1233 | pub average_play_time: u64, 1234 | /// Average player level on location 1235 | pub average_player_level: u64, 1236 | /// Extraction time limit 1237 | #[serde(rename = "escape_time_limit")] 1238 | pub escape_time_limit: u64, 1239 | /// Location rules 1240 | pub rules: String, 1241 | /// Location is secret 1242 | pub is_secret: bool, 1243 | /// Unknown type 1244 | #[serde(rename = "doors")] 1245 | pub doors: Vec, 1246 | /// ? 1247 | #[serde(rename = "tmp_location_field_remove_me")] 1248 | pub tmp_location_field_remove_me: u64, 1249 | /// Minimum distance to extraction from spawn 1250 | #[serde(rename = "MinDistToExitPoint")] 1251 | pub min_distance_to_exit_point: u64, 1252 | /// Minimum distance to "free point" from spawn 1253 | #[serde(rename = "MinDistToFreePoint")] 1254 | pub min_distance_to_free_point: u64, 1255 | /// Maximum distance to "free point" from spawn 1256 | #[serde(rename = "MaxDistToFreePoint")] 1257 | pub max_distance_to_free_point: u64, 1258 | /// Maximum number of bots per zone 1259 | pub max_bot_per_zone: u64, 1260 | /// Location open zones 1261 | pub open_zones: String, 1262 | /// ? 1263 | #[serde(rename = "OcculsionCullingEnabled")] 1264 | pub occlusion_culling_enabled: bool, 1265 | /// Location loot chance modifier 1266 | pub global_loot_chance_modifier: f64, 1267 | /// ? 1268 | pub old_spawn: bool, 1269 | /// ? 1270 | pub new_spawn: bool, 1271 | /// Maximum number of bots 1272 | pub bot_max: u64, 1273 | /// ? 1274 | pub bot_start: u64, 1275 | /// ? 1276 | pub bot_stop: u64, 1277 | /// ? 1278 | pub bot_max_time_player: u64, 1279 | /// ? 1280 | pub bot_spawn_time_on_min: u64, 1281 | /// ? 1282 | pub bot_spawn_time_on_max: u64, 1283 | /// ? 1284 | pub bot_spawn_time_off_min: u64, 1285 | /// ? 1286 | pub bot_spawn_time_off_max: u64, 1287 | /// ? 1288 | pub bot_max_player: u64, 1289 | /// Bot difficulty is "easy" 1290 | pub bot_easy: u64, 1291 | /// Bot difficulty is "normal" 1292 | pub bot_normal: u64, 1293 | /// Bot difficulty is "hard" 1294 | pub bot_hard: u64, 1295 | /// Bot difficulty is "impossible" 1296 | pub bot_impossible: u64, 1297 | /// ? 1298 | pub bot_assault: u64, 1299 | /// ? 1300 | pub bot_marksman: u64, 1301 | /// ? 1302 | pub disabled_scav_exits: String, 1303 | /// ? 1304 | pub access_keys: Vec, 1305 | /// ? 1306 | pub min_max_bots: Vec, 1307 | /// Bot stats modifier 1308 | pub bot_location_modifier: BotLocationModifier, 1309 | /// Extraction points 1310 | #[serde(rename = "exits")] 1311 | pub exits: Vec, 1312 | /// Location disabled for SCAVs 1313 | pub disabled_for_scav: bool, 1314 | /// Boss spawns 1315 | pub boss_location_spawn: Vec, 1316 | /// Location ID? 1317 | #[serde(rename = "Id")] 1318 | pub name_id: String, 1319 | /// Location ID? 1320 | #[serde(rename = "_Id")] 1321 | pub id: String, 1322 | /// Unknown type 1323 | pub loot: Vec, 1324 | /// Unknown type 1325 | pub spawn_areas: Vec, 1326 | /// Location banners 1327 | pub banners: Vec, 1328 | } 1329 | 1330 | /// Location scene prefab 1331 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1332 | pub struct Scene { 1333 | /// Scene path 1334 | pub path: String, 1335 | /// ? 1336 | pub rcid: String, 1337 | } 1338 | 1339 | /// Location path? 1340 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1341 | #[serde(rename_all = "PascalCase")] 1342 | pub struct Path { 1343 | /// ? 1344 | pub source: String, 1345 | /// ? 1346 | pub destination: String, 1347 | } 1348 | 1349 | /// Location preview 1350 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1351 | pub struct Preview { 1352 | /// Location preview path 1353 | pub path: String, 1354 | /// ? 1355 | pub rcid: String, 1356 | } 1357 | 1358 | /// Bot wave 1359 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1360 | #[serde(rename_all = "PascalCase")] 1361 | pub struct Wave { 1362 | /// Number of bots in wave? 1363 | #[serde(rename = "number")] 1364 | pub number: u64, 1365 | /// Minimum wave time 1366 | #[serde(rename = "time_min")] 1367 | pub time_min: u64, 1368 | /// Maximum wave time 1369 | #[serde(rename = "time_max")] 1370 | pub time_max: u64, 1371 | /// ? 1372 | #[serde(rename = "slots_min")] 1373 | pub slots_min: u64, 1374 | /// ? 1375 | #[serde(rename = "slots_max")] 1376 | pub slots_max: u64, 1377 | /// Wave spawn point 1378 | pub spawn_points: String, 1379 | /// Bot side 1380 | pub bot_side: Side, 1381 | /// Bot difficulty 1382 | pub bot_preset: BotDifficulty, 1383 | /// ? 1384 | #[serde(rename = "isPlayers")] 1385 | pub is_players: bool, 1386 | /// ? 1387 | pub wild_spawn_type: String, 1388 | } 1389 | 1390 | /// ? 1391 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1392 | pub struct MinMaxBot { 1393 | /// ? 1394 | pub min: u64, 1395 | /// ? 1396 | pub max: u64, 1397 | /// ? 1398 | #[serde(rename = "WildSpawnType")] 1399 | pub wild_spawn_type: String, 1400 | } 1401 | 1402 | /// Location bot stat modifier 1403 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1404 | #[serde(rename_all = "PascalCase")] 1405 | pub struct BotLocationModifier { 1406 | /// Bot accuracy speed modifier 1407 | pub accuracy_speed: u64, 1408 | /// Bot scattering modifier 1409 | pub scattering: u64, 1410 | /// Bot gain sight modifier 1411 | pub gain_sight: u64, 1412 | /// Bot marksmen accuracy modifier 1413 | #[serde(rename = "MarksmanAccuratyCoef")] 1414 | pub marksman_accuracy_coefficient: u64, 1415 | /// Bot visible distance modifier 1416 | pub visible_distance: u64, 1417 | } 1418 | 1419 | /// Extraction point 1420 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1421 | #[serde(rename_all = "PascalCase")] 1422 | pub struct Exit { 1423 | /// Extraction name 1424 | pub name: String, 1425 | /// ? 1426 | pub entry_points: String, 1427 | /// Extraction availability chance 1428 | pub chance: u64, 1429 | /// Extraction minimum availability time 1430 | pub min_time: u64, 1431 | /// Extraction maximum availability time 1432 | pub max_time: u64, 1433 | /// ? 1434 | pub players_count: u64, 1435 | /// ? 1436 | pub exfiltration_time: u64, 1437 | /// ? 1438 | pub passage_requirement: Option, 1439 | /// ? 1440 | pub exfiltration_type: Option, 1441 | /// ? 1442 | pub required_slot: Option, 1443 | /// ? 1444 | pub count: Option, 1445 | /// Extraction ID 1446 | pub id: String, 1447 | /// ? 1448 | pub requirement_tip: Option, 1449 | } 1450 | 1451 | /// Boss bots 1452 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1453 | #[serde(rename_all = "PascalCase")] 1454 | pub struct BossSpawn { 1455 | /// Boss name 1456 | #[serde(rename = "BossName")] 1457 | pub name: String, 1458 | /// Boss spawn chance 1459 | #[serde(rename = "BossChance")] 1460 | pub chance: u64, 1461 | /// Boss spawn zone 1462 | #[serde(rename = "BossZone")] 1463 | pub zone: String, 1464 | /// ? 1465 | #[serde(rename = "BossPlayer")] 1466 | pub player: bool, 1467 | /// Boss difficulty 1468 | #[serde(rename = "BossDifficult")] 1469 | pub difficulty: BotDifficulty, 1470 | /// Boss escort type 1471 | #[serde(rename = "BossEscortType")] 1472 | pub escort_type: String, 1473 | /// Boss escort difficulty 1474 | #[serde(rename = "BossEscortDifficult")] 1475 | pub escort_difficulty: BotDifficulty, 1476 | /// Number of boss escorts 1477 | #[serde(rename = "BossEscortAmount")] 1478 | pub escort_amount: String, 1479 | /// ? 1480 | pub time: i64, 1481 | } 1482 | 1483 | /// Boss difficulty 1484 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1485 | #[serde(rename_all = "lowercase")] 1486 | pub enum BotDifficulty { 1487 | /// Easy difficulty 1488 | Easy, 1489 | /// Normal difficulty 1490 | Normal, 1491 | /// Hard difficulty 1492 | Hard, 1493 | /// Impossible difficulty 1494 | Impossible, 1495 | } 1496 | 1497 | /// Location banner 1498 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1499 | pub struct Banner { 1500 | /// Banner ID 1501 | pub id: String, 1502 | /// Banner picture 1503 | pub pic: BannerPic, 1504 | } 1505 | 1506 | /// Location banner picture 1507 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1508 | pub struct BannerPic { 1509 | /// Picture path 1510 | pub path: String, 1511 | /// ? 1512 | pub rcid: String, 1513 | } 1514 | 1515 | #[derive(Debug, Deserialize)] 1516 | struct WeatherResponse { 1517 | #[serde(flatten)] 1518 | error: ErrorResponse, 1519 | data: Option, 1520 | } 1521 | 1522 | #[derive(Debug, Deserialize)] 1523 | struct WeatherData { 1524 | weather: Weather, 1525 | date: String, 1526 | time: String, 1527 | acceleration: u64, 1528 | } 1529 | 1530 | /// Weather 1531 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1532 | pub struct Weather { 1533 | /// Timestamp 1534 | pub timestamp: u64, 1535 | /// Cloud 1536 | pub cloud: f64, 1537 | /// Wind speed 1538 | pub wind_speed: u64, 1539 | /// Wind direction 1540 | pub wind_direction: u64, 1541 | /// Wind intensity 1542 | pub wind_gustiness: f64, 1543 | /// Rain 1544 | pub rain: u64, 1545 | /// Rain intensity 1546 | pub rain_intensity: f64, 1547 | /// Fog 1548 | pub fog: f64, 1549 | /// Temperature 1550 | pub temp: u64, 1551 | /// Atmospheric pressure 1552 | pub pressure: u64, 1553 | /// Date 1554 | pub date: String, 1555 | /// Time 1556 | pub time: String, 1557 | } 1558 | 1559 | #[derive(Debug, Deserialize)] 1560 | #[serde(rename_all = "camelCase")] 1561 | struct LocalizationResponse { 1562 | #[serde(flatten)] 1563 | error: ErrorResponse, 1564 | data: Option, 1565 | } 1566 | 1567 | /// EFT localization table 1568 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1569 | pub struct Localization { 1570 | /// Localization table for UI elements. 1571 | pub interface: HashMap, 1572 | /// Unknown type 1573 | #[serde(rename = "enum")] 1574 | pub enums: serde_json::Value, 1575 | /// Localization table for errors. 1576 | pub error: HashMap, 1577 | /// Localization table for automated messages (eg, from traders). 1578 | pub mail: HashMap, 1579 | /// Localization table for quest missions. 1580 | pub quest: HashMap, 1581 | /// ? 1582 | pub preset: HashMap, 1583 | /// Localization table for flea market categories. 1584 | pub handbook: HashMap, 1585 | /// Localization table for seasons. 1586 | pub season: HashMap, 1587 | /// Localization table for items. 1588 | #[serde(rename = "templates")] 1589 | pub items: HashMap, 1590 | /// Localization table for locations/maps. 1591 | pub locations: HashMap, 1592 | /// Localization table for banners. 1593 | pub banners: HashMap, 1594 | /// Localization table for traders. 1595 | #[serde(rename = "trading")] 1596 | pub traders: HashMap, 1597 | } 1598 | 1599 | /// Quest 1600 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1601 | #[serde(rename_all = "camelCase")] 1602 | pub struct Quest { 1603 | /// Quest name 1604 | pub name: String, 1605 | /// Quest description 1606 | pub description: Option, 1607 | /// Quest note 1608 | pub note: Option, 1609 | /// Quest fail message 1610 | pub fail_message_text: Option, 1611 | /// Quest start message 1612 | pub started_message_text: Option, 1613 | /// Quest success message 1614 | pub success_message_text: Option, 1615 | /// Quest conditions 1616 | pub conditions: HashMap, 1617 | /// Quest location 1618 | pub location: String, 1619 | } 1620 | 1621 | /// ? 1622 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1623 | #[serde(rename_all = "PascalCase")] 1624 | pub struct Preset { 1625 | /// Preset name 1626 | pub name: Option, 1627 | } 1628 | 1629 | /// Item localization 1630 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1631 | #[serde(rename_all = "PascalCase")] 1632 | pub struct ItemLocalization { 1633 | /// Item name 1634 | pub name: String, 1635 | /// Item short name 1636 | pub short_name: String, 1637 | /// Item description 1638 | pub description: String, 1639 | } 1640 | 1641 | /// Location localization 1642 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1643 | #[serde(rename_all = "PascalCase")] 1644 | pub struct LocationLocalization { 1645 | /// Location name 1646 | pub name: String, 1647 | /// Location description 1648 | pub description: String, 1649 | } 1650 | 1651 | /// Banner localization 1652 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1653 | #[serde(rename_all = "PascalCase")] 1654 | pub struct BannerLocalization { 1655 | /// Banner name 1656 | pub name: Option, 1657 | /// Banner description 1658 | pub description: Option, 1659 | } 1660 | 1661 | /// Trader localization 1662 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 1663 | #[serde(rename_all = "PascalCase")] 1664 | pub struct TraderLocalization { 1665 | /// Trader full name 1666 | pub full_name: String, 1667 | /// Trader first name 1668 | pub first_name: String, 1669 | /// Trader nickname 1670 | pub nickname: String, 1671 | /// Trader location 1672 | pub location: String, 1673 | /// Trader description 1674 | pub description: String, 1675 | } 1676 | 1677 | #[derive(Debug, Deserialize)] 1678 | #[serde(rename_all = "camelCase")] 1679 | struct PricesResponse { 1680 | #[serde(flatten)] 1681 | error: ErrorResponse, 1682 | data: Option>, 1683 | } 1684 | 1685 | impl Tarkov { 1686 | /// Get a list of all in-game items. 1687 | pub async fn get_items(&self) -> Result> { 1688 | let url = format!("{}/client/items", PROD_ENDPOINT); 1689 | let res: ItemsResponse = self.post_json(&url, &Request { crc: 0 }).await?; 1690 | 1691 | handle_error(res.error, res.data) 1692 | } 1693 | 1694 | /// Get a list of all in-game item prices. 1695 | pub async fn get_item_prices(&self) -> Result> { 1696 | let url = format!("{}/client/items/prices", PROD_ENDPOINT); 1697 | let res: PricesResponse = self.post_json(&url, &Request { crc: 0 }).await?; 1698 | 1699 | handle_error(res.error, res.data) 1700 | } 1701 | 1702 | /// Get a list of all locations/maps. 1703 | pub async fn get_locations(&self) -> Result { 1704 | let url = format!("{}/client/locations", PROD_ENDPOINT); 1705 | let res: LocationsResponse = self.post_json(&url, &Request { crc: 0 }).await?; 1706 | 1707 | handle_error(res.error, res.data) 1708 | } 1709 | 1710 | /// Get the current forecast and time. 1711 | pub async fn get_weather(&self) -> Result { 1712 | let url = format!("{}/client/weather", PROD_ENDPOINT); 1713 | let res: WeatherResponse = self.post_json(&url, &{}).await?; 1714 | 1715 | handle_error(res.error, res.data).map(|w| w.weather) 1716 | } 1717 | 1718 | /// Get the localization table. Pass a valid ISO 639-1 language code. 1719 | pub async fn get_i18n(&self, language: &str) -> Result { 1720 | if language.is_empty() { 1721 | return Err(Error::InvalidParameters); 1722 | } 1723 | 1724 | let url = format!("{}/client/locale/{}", PROD_ENDPOINT, language); 1725 | let res: LocalizationResponse = self.post_json(&url, &{}).await?; 1726 | 1727 | handle_error(res.error, res.data) 1728 | } 1729 | } 1730 | --------------------------------------------------------------------------------