Add real price ratio

This commit is contained in:
SkyfallWasTaken 2024-07-23 10:39:11 +01:00
parent ca2acbffe6
commit 17c19361ab
5 changed files with 64 additions and 14 deletions

7
Cargo.lock generated
View file

@ -37,6 +37,7 @@ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"getrandom", "getrandom",
"indoc", "indoc",
"maplit",
"pretty_assertions", "pretty_assertions",
"reqwest", "reqwest",
"scraper", "scraper",
@ -631,6 +632,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.11.0" version = "0.11.0"

View file

@ -25,6 +25,7 @@ reqwest = "0.12.5"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
maplit = "1.0.2"
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["getrandom"] ignored = ["getrandom"]

View file

@ -12,6 +12,10 @@
- `NTFY_URL` - URL for ntfy - `NTFY_URL` - URL for ntfy
- `SLACK_GROUP_ID` - ID of the Slack group to ping - `SLACK_GROUP_ID` - ID of the Slack group to ping
## Key-value keys
- `real_prices` - stores the real-world prices of items. equivalent to a `HashMap<String, i32>`, where `String` is the `id` parameter. prices are in USD.
- `items` - stores old items (you don't need to worry about this one)
## Tech Stack ## Tech Stack
- **Cloudflare Workers** for running the monitor on the edge. - **Cloudflare Workers** for running the monitor on the edge.
- **Rust** for the monitor's code. I love its type safety, as well as libraries such as `serde`. - **Rust** for the monitor's code. I love its type safety, as well as libraries such as `serde`.

View file

@ -3,7 +3,11 @@ use serde_json::json;
use crate::items::ShopItem; use crate::items::ShopItem;
pub fn format_item_diff(old: &ShopItem, new: &ShopItem) -> Option<String> { pub fn format_item_diff(
old: &ShopItem,
new: &ShopItem,
real_price: Option<&i32>,
) -> Option<String> {
if old == new { if old == new {
// The items are the exact same // The items are the exact same
return None; return None;
@ -19,13 +23,21 @@ pub fn format_item_diff(old: &ShopItem, new: &ShopItem) -> Option<String> {
if old.price != new.price { if old.price != new.price {
result.push(format!( result.push(format!(
"*Price:* {} → {} {}", "*Price:* {} → {} {}{}",
old.price, old.price,
new.price, new.price,
if old.price > new.price { if old.price > new.price {
"🔽" "🔽"
} else { } else {
"🔼" "🔼"
},
if let Some(real_price) = real_price {
format!(
" _(${real_price} - ${}/hr)_",
(*real_price as f32) / (new.price as f32)
)
} else {
"".into()
} }
)); ));
} }
@ -257,21 +269,23 @@ mod diff_tests {
let old = ShopItem { let old = ShopItem {
full_name: "Test".into(), full_name: "Test".into(),
price: 1, price: 1,
id: "1".into(),
..Default::default() ..Default::default()
}; };
let new = ShopItem { let new = ShopItem {
full_name: "Test".into(), full_name: "Test".into(),
price: 2, price: 2,
id: "1".into(),
..Default::default() ..Default::default()
}; };
assert_eq!( assert_eq!(
format_item_diff(&old, &new), format_item_diff(&old, &new, Some(&50)), // Let's say it's $50
Some( Some(
indoc! {" indoc! {"
*Name:* Test *Name:* Test
*Price:* 1 2 🔼"} *Price:* 1 2 🔼 _($50 - $25/hr)_"}
.into() .into()
) )
); );
@ -282,17 +296,19 @@ mod diff_tests {
let old = ShopItem { let old = ShopItem {
full_name: "Test".into(), full_name: "Test".into(),
description: Some("Lorem ipsum".into()), description: Some("Lorem ipsum".into()),
price: 2,
..Default::default() ..Default::default()
}; };
let new = ShopItem { let new = ShopItem {
full_name: "Test".into(), full_name: "Test".into(),
description: Some("Dolor sit amet".into()), description: Some("Dolor sit amet".into()),
price: 2,
..Default::default() ..Default::default()
}; };
assert_eq!( assert_eq!(
format_item_diff(&old, &new), format_item_diff(&old, &new, Some(&50)),
Some( Some(
indoc! {" indoc! {"
*Name:* Test *Name:* Test
@ -317,7 +333,7 @@ mod diff_tests {
}; };
assert_eq!( assert_eq!(
format_item_diff(&old, &new), format_item_diff(&old, &new, Some(&50)),
Some( Some(
indoc! {" indoc! {"
*Name:* Test *Name:* Test
@ -332,17 +348,19 @@ mod diff_tests {
let old = ShopItem { let old = ShopItem {
full_name: "Test".into(), full_name: "Test".into(),
stock: Some(10), stock: Some(10),
price: 2,
..Default::default() ..Default::default()
}; };
let new = ShopItem { let new = ShopItem {
full_name: "Test".into(), full_name: "Test".into(),
stock: None, stock: None,
price: 2,
..Default::default() ..Default::default()
}; };
assert_eq!( assert_eq!(
format_item_diff(&old, &new), format_item_diff(&old, &new, Some(&50)),
Some( Some(
indoc! {" indoc! {"
*Name:* Test *Name:* Test
@ -367,7 +385,7 @@ mod diff_tests {
}; };
assert_eq!( assert_eq!(
format_item_diff(&old, &new), format_item_diff(&old, &new, Some(&50)),
Some( Some(
indoc! {" indoc! {"
*Name:* Test *Name:* Test
@ -392,7 +410,7 @@ mod diff_tests {
}; };
assert_eq!( assert_eq!(
format_item_diff(&old, &new), format_item_diff(&old, &new, Some(&50)),
Some( Some(
indoc! {" indoc! {"
*Name:* Test *Name:* Test
@ -410,6 +428,6 @@ mod diff_tests {
..Default::default() ..Default::default()
}; };
assert_eq!(format_item_diff(&item, &item), None); assert_eq!(format_item_diff(&item, &item, Some(&50)), None);
} }
} }

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use items::ShopItems; use items::ShopItems;
use reqwest::Client; use reqwest::Client;
use worker::*; use worker::*;
@ -48,9 +50,13 @@ async fn run_scrape(env: Env) -> Result<String> {
kv.put("items", &available_items)?.execute().await?; kv.put("items", &available_items)?.execute().await?;
return Ok("No old items found, storing new items".into()); return Ok("No old items found, storing new items".into());
}; };
let Some(real_prices) = kv.get("real_prices").json::<HashMap<String, i32>>().await? else {
console_debug!("No real prices found!");
return Err("No real prices found! This is a bug.".into());
};
// Compare the old items with the new items. // Compare the old items with the new items.
let result = diff_old_new_items(&old_items, &available_items); let result = diff_old_new_items(&old_items, &available_items, real_prices);
// Check if there are any updates. // Check if there are any updates.
if result.is_empty() { if result.is_empty() {
@ -89,7 +95,11 @@ async fn run_scrape(env: Env) -> Result<String> {
Ok(result.join("\n\n")) Ok(result.join("\n\n"))
} }
fn diff_old_new_items(old_items: &ShopItems, new_items: &ShopItems) -> Vec<String> { fn diff_old_new_items(
old_items: &ShopItems,
new_items: &ShopItems,
real_prices: HashMap<String, i32>,
) -> Vec<String> {
let mut result: Vec<String> = Vec::new(); let mut result: Vec<String> = Vec::new();
for item in new_items { for item in new_items {
// TODO: not very efficient. // TODO: not very efficient.
@ -97,7 +107,7 @@ fn diff_old_new_items(old_items: &ShopItems, new_items: &ShopItems) -> Vec<Strin
match old_item { match old_item {
Some(old) => { Some(old) => {
if let Some(diff) = format::format_item_diff(old, item) { if let Some(diff) = format::format_item_diff(old, item, real_prices.get(&item.id)) {
result.push(diff); result.push(diff);
} }
} }
@ -123,6 +133,7 @@ mod diff_old_new_items_tests {
use indoc::formatdoc; use indoc::formatdoc;
use items::ShopItem; use items::ShopItem;
use maplit::hashmap;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@ -130,12 +141,14 @@ mod diff_old_new_items_tests {
let item_1 = ShopItem { let item_1 = ShopItem {
full_name: "Item 1".into(), full_name: "Item 1".into(),
description: Some("Description 1".into()), description: Some("Description 1".into()),
price: 200,
id: "1".into(), id: "1".into(),
..Default::default() ..Default::default()
}; };
let item_2 = ShopItem { let item_2 = ShopItem {
full_name: "Item 2".into(), full_name: "Item 2".into(),
description: Some("Description 2".into()), description: Some("Description 2".into()),
price: 50,
id: "2".into(), id: "2".into(),
..Default::default() ..Default::default()
}; };
@ -143,7 +156,14 @@ mod diff_old_new_items_tests {
let old_items = vec![item_1.clone(), item_2.clone()]; let old_items = vec![item_1.clone(), item_2.clone()];
let new_items = vec![item_1.clone()]; let new_items = vec![item_1.clone()];
let result = diff_old_new_items(&old_items, &new_items); let result = diff_old_new_items(
&old_items,
&new_items,
hashmap! {
"1".into() => 100,
"2".into() => 200,
},
);
assert_eq!(result.len(), 1); assert_eq!(result.len(), 1);
assert_eq!( assert_eq!(