added env file and the ability to set a interval for data to be grabbed

This commit is contained in:
Steven 2023-07-28 22:45:56 -04:00
parent 5d71ada8c9
commit f9e7f97f00
4 changed files with 178 additions and 72 deletions

1
.env Normal file
View file

@ -0,0 +1 @@
UPDATE_INTERVAL=12

98
Cargo.lock generated
View file

@ -17,6 +17,21 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -80,6 +95,21 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.7" version = "0.15.7"
@ -109,6 +139,12 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "0.3.6" version = "0.3.6"
@ -328,6 +364,29 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.4.0" version = "0.4.0"
@ -453,7 +512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -461,6 +520,8 @@ dependencies = [
name = "mtg_seller_bot" name = "mtg_seller_bot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono",
"dotenv",
"indicatif", "indicatif",
"reqwest", "reqwest",
"serde", "serde",
@ -486,6 +547,15 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "num-traits"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.16.0" version = "1.16.0"
@ -842,6 +912,17 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -997,6 +1078,12 @@ dependencies = [
"try-lock", "try-lock",
] ]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -1101,6 +1188,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.1",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.45.0"

View file

@ -6,6 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
chrono = "0.4.26"
dotenv = "0.15.0"
indicatif = "0.17.5" indicatif = "0.17.5"
reqwest = { version = "0.11.18", features = ["json"] } reqwest = { version = "0.11.18", features = ["json"] }
serde = { version = "1.0.178", features = ["derive"]} serde = { version = "1.0.178", features = ["derive"]}

View file

@ -1,107 +1,114 @@
/// This program utilizes several external libraries to perform its functions, such as dotenv, serde, tokio, etc.
use dotenv;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, env, fs::File, fs::OpenOptions, io::BufReader}; use std::{collections::HashMap, env, fs::File, fs::OpenOptions, io::BufReader};
use tokio::time::Duration; use tokio::time::Duration;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
/// This structure defines a Card object, consisting of a name and a hashmap of prices.
/// A struct to represent a Card returned from the API.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
struct Card { struct Card {
/// The name of the card.
name: String, name: String,
/// The prices of the card in various formats.
prices: HashMap<String, Option<String>>, prices: HashMap<String, Option<String>>,
} }
/// A struct to represent a Card from the local JSON file. /// This structure defines a CardFromFile object, consisting of a name, count, and a USD value.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
struct CardFromFile { struct CardFromFile {
/// The name of the card.
name: String, name: String,
/// The count of this card.
count: usize, count: usize,
/// The value of this card in USD.
usd_value: Option<String>, usd_value: Option<String>,
} }
/// A struct to represent a collection of Cards from the local JSON file. /// This structure defines a CardFile object, consisting of a vector of CardFromFile objects.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct CardFile { struct CardFile {
/// The list of cards.
cards: Vec<CardFromFile>, cards: Vec<CardFromFile>,
} }
/// The main function. /// The main function of the program. It retrieves card price information from an external API and updates a local JSON file.
/// /// It accepts a path to a JSON file as an argument and also uses an environment variable, UPDATE_INTERVAL, to determine the frequency of its update cycle.
/// This function reads a local JSON file of cards, sends an API request for each card to get the current price in USD,
/// compares the fetched price with the stored price in the local file, and updates the file if there is any difference.
///
/// Note: There is a delay of 100ms between each API request as per the API rules.
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<std::io::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect(); dotenv::dotenv().ok();
if args.len() < 2 {
eprintln!("Please provide the path to the JSON file as an argument.");
return Ok(());
}
let file_path = &args[1]; // Retrieve the update interval from the .env file or return an error if not present.
let file = match File::open(file_path) { let update_interval_str = match dotenv::var("UPDATE_INTERVAL") {
Ok(file) => file, Ok(val) => val,
Err(error) => { Err(_) => {
eprintln!("There was a problem opening the file: {:?}", error); eprintln!("UPDATE_INTERVAL is not defined in the .env file");
return Err(Box::new(std::io::Error::new( return Err("UPDATE_INTERVAL is not defined in the .env file".into());
std::io::ErrorKind::NotFound,
"File not found",
)));
} }
}; };
// Attempt to parse the update interval as a u64 or return an error if it fails.
let reader = BufReader::new(file); let update_interval = match update_interval_str.parse::<u64>() {
let mut cards_data: CardFile = serde_json::from_reader(reader).unwrap(); Ok(val) => val,
Err(_) => {
eprintln!("UPDATE_INTERVAL is not a valid number");
return Err("UPDATE_INTERVAL is not a valid number".into());
}
};
let pb = ProgressBar::new(cards_data.cards.len() as u64); let update_interval = std::time::Duration::from_secs(update_interval * 3600);
let style = ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
.unwrap();
let style = style.progress_chars("#>-");
pb.set_style(style);
for card_from_file in &mut cards_data.cards { let mut interval = tokio::time::interval(update_interval);
let request_url = format!( loop {
"https://api.scryfall.com/cards/named?exact={}", interval.tick().await;
card_from_file.name
);
let response = reqwest::get(&request_url).await
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err.to_string()))?;
let card: Card = response.json().await
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err.to_string()))?;
// Retrieve the file path from the program's arguments or return an error if not present.
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Please provide the path to the JSON file as an argument.");
return Ok(());
}
if let Some(price) = card.prices.get("usd") { let file_path = &args[1];
if let Some(price_str) = price { let file = File::open(file_path)?;
if card_from_file.usd_value.as_ref() != Some(price_str) {
card_from_file.usd_value = Some(price_str.clone()); let reader = BufReader::new(file);
let mut cards_data: CardFile = serde_json::from_reader(reader)?;
// Setting up a progress bar for visual representation of the card processing progress.
let pb = ProgressBar::new(cards_data.cards.len() as u64);
let style = ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
.unwrap();
let style = style.progress_chars("#>-");
pb.set_style(style);
// For each card in the input file, retrieve the current price information and update the local data if necessary.
for card_from_file in &mut cards_data.cards {
let request_url = format!(
"https://api.scryfall.com/cards/named?exact={}",
card_from_file.name
);
let response = reqwest::get(&request_url).await?;
let card: Card = response.json().await?;
if let Some(price) = card.prices.get("usd") {
if let Some(price_str) = price {
if card_from_file.usd_value.as_ref() != Some(price_str) {
card_from_file.usd_value = Some(price_str.clone());
}
} }
} }
// Increment the progress bar and pause for a brief period.
pb.inc(1);
tokio::time::sleep(Duration::from_millis(100)).await;
} }
pb.inc(1); // Mark the progress bar as completed.
tokio::time::sleep(Duration::from_millis(100)).await; pb.finish_with_message("Completed!");
// Write the updated card data back to the input file.
let file = OpenOptions::new()
.write(true)
.truncate(true)
.open(file_path)?;
serde_json::to_writer_pretty(file, &cards_data)?;
} }
pb.finish_with_message("Completed!");
let file = OpenOptions::new()
.write(true)
.truncate(true)
.open(file_path)
.unwrap();
serde_json::to_writer_pretty(file, &cards_data).unwrap();
Ok(())
} }