Merge pull request #2 from Death916/rust

Rust
This commit is contained in:
Death916 2026-03-24 05:15:49 -07:00 committed by GitHub
commit 4101bf7587
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 6751 additions and 11 deletions

6
.gitignore vendored
View file

@ -13,3 +13,9 @@ chromedriver
*.flox
*.direnv
.envrc
# Added by cargo
*/target

6022
rustclock/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

14
rustclock/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "rustclock"
version = "0.2.0"
edition = "2024"
[dependencies]
chrono = "0.4.44"
iced = { version = "0.14.0", features = ["image", "tokio"] }
reqwest = "0.13.2"
rss = "2.0.12"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
tokio = { version = "1.50.0", features = ["full"] }
ureq = "3.2.0"

View file

@ -0,0 +1,32 @@
[
{"abbr": "ARI", "name": "Arizona Diamondbacks", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/ari.png"},
{"abbr": "ATL", "name": "Atlanta Braves", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/atl.png"},
{"abbr": "BAL", "name": "Baltimore Orioles", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/bal.png"},
{"abbr": "BOS", "name": "Boston Red Sox", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/bos.png"},
{"abbr": "CHC", "name": "Chicago Cubs", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/chc.png"},
{"abbr": "CHW", "name": "Chicago White Sox", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/chw.png"},
{"abbr": "CIN", "name": "Cincinnati Reds", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/cin.png"},
{"abbr": "CLE", "name": "Cleveland Indians", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/cle.png"},
{"abbr": "COL", "name": "Colorado Rockies", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/col.png"},
{"abbr": "DET", "name": "Detroit Tigers", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/det.png"},
{"abbr": "HOU", "name": "Houston Astros", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/hou.png"},
{"abbr": "KCR", "name": "Kansas City Royals", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/kc.png"},
{"abbr": "LAA", "name": "Los Angeles Angels", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/laa.png"},
{"abbr": "LAD", "name": "Los Angeles Dodgers", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/lad.png"},
{"abbr": "MIA", "name": "Miami Marlins", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/mia.png"},
{"abbr": "MIL", "name": "Milwaukee Brewers", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/mil.png"},
{"abbr": "MIN", "name": "Minnesota Twins", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/min.png"},
{"abbr": "NYM", "name": "New York Mets", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/nym.png"},
{"abbr": "NYY", "name": "New York Yankees", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/nyy.png"},
{"abbr": "OAK", "name": "Oakland Athletics", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/oak.png"},
{"abbr": "PHI", "name": "Philadelphia Phillies", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/phi.png"},
{"abbr": "PIT", "name": "Pittsburgh Pirates", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/pit.png"},
{"abbr": "SDP", "name": "San Diego Padres", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/sd.png"},
{"abbr": "SFG", "name": "San Francisco Giants", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/sf.png"},
{"abbr": "SEA", "name": "Seattle Mariners", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/sea.png"},
{"abbr": "STL", "name": "St. Louis Cardinals", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/stl.png"},
{"abbr": "TBR", "name": "Tampa Bay Rays", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/tb.png"},
{"abbr": "TEX", "name": "Texas Rangers", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/tex.png"},
{"abbr": "TOR", "name": "Toronto Blue Jays", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/tor.png"},
{"abbr": "WSN", "name": "Washington Nationals", "logo": "http://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/wsh.png"}
]

View file

@ -0,0 +1,32 @@
[
{"abbr": "ATL", "name": "Hawks", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/atl.png"},
{"abbr": "BOS", "name": "Celtics", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/bos.png"},
{"abbr": "BKN", "name": "Nets", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/bkn.png"},
{"abbr": "CHA", "name": "Hornets", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/cha.png"},
{"abbr": "CHI", "name": "Bulls", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/chi.png"},
{"abbr": "CLE", "name": "Cavaliers", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/cle.png"},
{"abbr": "DAL", "name": "Mavericks", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/dal.png"},
{"abbr": "DEN", "name": "Nuggets", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/den.png"},
{"abbr": "DET", "name": "Pistons", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/det.png"},
{"abbr": "GSW", "name": "Warriors", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/gsw.png"},
{"abbr": "HOU", "name": "Rockets", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/hou.png"},
{"abbr": "IND", "name": "Pacers", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/ind.png"},
{"abbr": "LAC", "name": "Clippers", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/lac.png"},
{"abbr": "LAL", "name": "Lakers", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/lal.png"},
{"abbr": "MEM", "name": "Grizzlies", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/mem.png"},
{"abbr": "MIA", "name": "Heat", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/mia.png"},
{"abbr": "MIL", "name": "Bucks", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/mil.png"},
{"abbr": "MIN", "name": "Timberwolves", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/min.png"},
{"abbr": "NOP", "name": "Pelicans", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/no.png"},
{"abbr": "NYK", "name": "Knicks", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/ny.png"},
{"abbr": "OKC", "name": "Thunder", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/okc.png"},
{"abbr": "ORL", "name": "Magic", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/orl.png"},
{"abbr": "PHI", "name": "76ers", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/phi.png"},
{"abbr": "PHX", "name": "Suns", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/phx.png"},
{"abbr": "POR", "name": "Trail Blazers", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/por.png"},
{"abbr": "SAC", "name": "Kings", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/sac.png"},
{"abbr": "SAS", "name": "Spurs", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/sa.png"},
{"abbr": "TOR", "name": "Raptors", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/tor.png"},
{"abbr": "UTA", "name": "Jazz", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/utah.png"},
{"abbr": "WAS", "name": "Wizards", "logo": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/wsh.png"}
]

167
rustclock/src/main.rs Normal file
View file

@ -0,0 +1,167 @@
// #![allow(dead_code)]
mod news;
mod panes;
mod sports;
mod weather;
use chrono::{DateTime, Local};
use iced::Element;
use iced::Subscription;
use iced::Task;
use iced::time::Duration;
use iced::widget::image::Handle;
use iced::widget::pane_grid;
use iced::widget::pane_grid::Configuration;
use sports::Game;
use std::collections::HashMap;
const CLOCK_UPDATE_TIME_MS: u64 = 1500;
const UPDATE_SPORTS_TIME_MINS: u64 = 5;
const WEATHER_UPDATE_TIME_MINS: u64 = 30;
pub fn main() -> iced::Result {
iced::application(
|| (RustClock::default(), Task::none()), // Wrap it in a closure
RustClock::update,
RustClock::view,
)
.title("RustClock")
.subscription(RustClock::subscription)
.run()
}
#[derive(Debug, Clone)]
enum PaneType {
MlbPane,
NflPane,
NbaPane,
Weather,
Clock,
News,
}
#[derive(Debug, Clone)]
enum Message {
PaneDragged(pane_grid::DragEvent),
PaneResized(pane_grid::ResizeEvent),
RunSportsUpdate,
UpdateTime,
UpdateWeather,
}
#[derive(Debug)]
struct RustClock {
current_time: DateTime<Local>,
next_alarm: Option<DateTime<Local>>,
news: Vec<String>,
location: String,
nba_scores: Vec<Game>,
mlb_scores: Vec<Game>,
panes: pane_grid::State<PaneType>,
nba_logos: HashMap<String, Handle>,
mlb_logos: HashMap<String, Handle>,
weather_handle: Option<Handle>,
}
impl RustClock {
fn update(&mut self, message: Message) {
match message {
Message::PaneDragged(pane_grid::DragEvent::Dropped { pane, target }) => {
self.panes.drop(pane, target);
}
Message::PaneDragged(_) => {}
Message::PaneResized(pane_grid::ResizeEvent { split, ratio }) => {
self.panes.resize(split, ratio);
}
Message::RunSportsUpdate => {
self.nba_scores = sports::update_nba();
self.mlb_scores = sports::update_mlb();
}
Message::UpdateTime => {
self.current_time = Local::now();
}
Message::UpdateWeather => self.weather_handle = weather::get_weather(),
}
}
fn subscription(&self) -> Subscription<Message> {
Subscription::batch(vec![
iced::time::every(Duration::from_millis(CLOCK_UPDATE_TIME_MS))
.map(|_| Message::UpdateTime),
iced::time::every(Duration::from_mins(UPDATE_SPORTS_TIME_MINS))
.map(|_| Message::RunSportsUpdate),
iced::time::every(Duration::from_mins(WEATHER_UPDATE_TIME_MINS))
.map(|_| Message::UpdateWeather),
])
}
fn view(state: &RustClock) -> Element<'_, Message> {
pane_grid(&state.panes, |_panes, pane_state, _is_maximized| {
let content: Element<'_, Message> = match pane_state {
PaneType::NbaPane => panes::render_nba_pane(&state.nba_scores, &state.nba_logos),
PaneType::NflPane => panes::render_nfl_pane(),
PaneType::News => panes::render_news_pane(),
PaneType::MlbPane => panes::render_mlb_pane(&state.mlb_scores, &state.mlb_logos),
PaneType::Clock => panes::render_clock_pane(),
PaneType::Weather => {
panes::render_weather_pane(&state.weather_handle, &state.location)
}
};
pane_grid::Content::new(content)
})
.on_drag(Message::PaneDragged)
.on_resize(10, Message::PaneResized)
.into()
}
}
impl Default for RustClock {
fn default() -> Self {
let mlb_logos_bytes = sports::get_mlb_logos();
let nba_logos_bytes = sports::get_nba_logos();
let mlb_logos: HashMap<String, Handle> = mlb_logos_bytes
.into_iter()
.map(|(k, v)| (k, Handle::from_bytes(v)))
.collect();
let nba_logos: HashMap<String, Handle> = nba_logos_bytes
.into_iter()
.map(|(k, v)| (k, Handle::from_bytes(v)))
.collect();
RustClock {
current_time: Local::now(),
next_alarm: None,
news: Vec::new(),
location: "Sacramento".to_string(),
nba_scores: { sports::update_nba() },
mlb_scores: { sports::update_mlb() },
mlb_logos,
nba_logos,
weather_handle: weather::get_weather(),
panes: {
let config = Configuration::Split {
axis: pane_grid::Axis::Horizontal,
ratio: 0.05,
a: Box::new(Configuration::Pane(PaneType::Clock)),
b: Box::new(Configuration::Split {
axis: pane_grid::Axis::Vertical,
ratio: 0.25,
a: Box::new(Configuration::Pane(PaneType::NbaPane)),
b: Box::new(Configuration::Split {
axis: pane_grid::Axis::Vertical,
ratio: 0.65,
a: Box::new(Configuration::Pane(PaneType::Weather)),
b: Box::new(Configuration::Split {
axis: pane_grid::Axis::Horizontal,
ratio: 0.7,
a: Box::new(Configuration::Pane(PaneType::MlbPane)),
b: Box::new(Configuration::Pane(PaneType::NflPane)),
}),
}),
}),
};
pane_grid::State::with_configuration(config)
},
}
}
}

60
rustclock/src/news.rs Normal file
View file

@ -0,0 +1,60 @@
use rss::Channel;
use std::fs::File;
use std::io::{BufRead, BufReader};
pub async fn get_news() -> Vec<Channel> {
let feeds = File::open("../feeds.txt");
let mut feed_vec = Vec::new();
let mut news = Vec::new();
match feeds {
Ok(file) => {
let reader = BufReader::new(file);
for line in reader.lines() {
if let Ok(feed) = line {
feed_vec.push(feed);
}
}
}
Err(e) => {
eprintln!("Error opening file: {}", e);
}
}
for feed in feed_vec.iter() {
let feed_url = feed.as_str();
let content = reqwest::get(feed_url).await;
match content {
Ok(contents) => {
let contents = contents.bytes().await;
if let Ok(response) = contents {
let channel = Channel::read_from(&response[..]);
// dbg!(&channel);
match channel {
Ok(feed) => {
dbg!("Title: {}", feed.title());
dbg!("Link: {}", feed.link());
news.push(feed);
}
Err(e) => {
eprintln!("Error parsing feed: {},{}", feed_url, e);
}
}
}
}
Err(e) => {
eprintln!("Error fetching feed: {},{}", feed_url, e);
continue;
}
}
}
news
}
#[tokio::test]
async fn test() {
let news = get_news().await;
assert!(news.len() > 0);
}

157
rustclock/src/panes.rs Normal file
View file

@ -0,0 +1,157 @@
use chrono::Local;
use iced::Border;
use iced::Element;
use iced::Fill;
use iced::widget::{column, container, image, row, scrollable, text};
use std::collections::HashMap;
use iced::widget::scrollable::{Direction, Scrollbar};
use crate::Message;
use crate::sports::Game;
use iced::widget::image::Handle;
pub fn render_nba_pane<'a>(
games: &'a [Game],
logos: &'a HashMap<String, Handle>,
) -> Element<'a, Message> {
scrollable(column(games.iter().map(|game| {
let Some(team1_logo) = logos.get(&game.team1) else {
return text(format!("Error: Team 1 logo not found for {}", game.team1)).into();
};
let Some(team2_logo) = logos.get(&game.team2) else {
return text("Error: Team 2 logo not found").into();
};
container(
column![
row![
image(team1_logo.clone()).width(30).height(30),
text(&game.team1).size(20).width(Fill),
image(team2_logo.clone()).width(30).height(30),
text(&game.team2).size(20).width(Fill),
],
row![
text(&game.score1).size(20).width(Fill),
text(&game.score2).size(20).width(Fill),
],
text(format!("Period: {}", game.period)).size(14),
]
.padding(10),
)
.padding(5)
.width(Fill)
.style(|_| container::Style {
background: Some(iced::Background::Color(iced::Color::from_rgb(
0.2, 0.2, 0.2,
))),
border: Border {
width: 1.0,
color: iced::Color::WHITE,
radius: 0.0.into(),
},
text_color: Some(iced::Color::WHITE),
snap: true,
shadow: iced::Shadow {
color: iced::Color::BLACK,
offset: iced::Vector::new(0.0, 0.0),
blur_radius: 10.0,
},
})
.into()
})))
.direction(Direction::Vertical(Scrollbar::hidden()))
.width(50)
.into()
}
pub fn render_nfl_pane<'a>() -> Element<'a, Message> {
text("NFL").into()
}
pub fn render_news_pane<'a>() -> Element<'a, Message> {
text("News").into()
}
pub fn render_mlb_pane<'a>(
games: &'a [Game],
logos: &'a HashMap<String, Handle>,
) -> Element<'a, Message> {
scrollable(column(games.iter().map(|game| {
let Some(team1_logo) = logos.get(&game.team1) else {
return text(format!("Error: Team 1 logo not found for {}", game.team1)).into();
};
let Some(team2_logo) = logos.get(&game.team2) else {
return text("Error: Team 2 logo not found").into();
};
container(
column![
row![
image(team1_logo.clone()).width(30).height(30),
text(&game.team1).size(20).width(Fill),
image(team2_logo.clone()).width(30).height(30),
text(&game.team2).size(20).width(Fill),
],
row![
text(&game.score1).size(20).width(Fill),
text(&game.score2).size(20).width(Fill),
],
text(format!("Period: {}", game.period)).size(14),
]
.padding(10),
)
.padding(5)
.width(Fill)
.style(|_| container::Style {
background: Some(iced::Background::Color(iced::Color::from_rgb(
0.2, 0.2, 0.2,
))),
border: Border {
width: 1.0,
color: iced::Color::WHITE,
radius: 0.0.into(),
},
text_color: Some(iced::Color::WHITE),
snap: true,
shadow: iced::Shadow {
color: iced::Color::BLACK,
offset: iced::Vector::new(0.0, 0.0),
blur_radius: 10.0,
},
})
.into()
})))
.direction(Direction::Vertical(Scrollbar::hidden()))
.into()
}
pub fn render_clock_pane<'a>() -> Element<'a, Message> {
container(row![
text(Local::now().format("%m/%d %H:%M:%S").to_string()).size(30)
])
.align_x(iced::Alignment::Center)
.into()
}
pub fn render_weather_pane<'a>(
weather_handle: &'a Option<Handle>,
location: &'a str,
) -> Element<'a, Message> {
let Some(weather_img) = weather_handle else {
return text("Weather image not loaded").into();
};
container(
column![
text("Weather").size(50),
image(weather_img.clone()).width(Fill),
text(location).size(30),
]
.padding(5)
.align_x(iced::Alignment::Center),
)
.width(Fill)
.height(Fill)
.center_x(Fill)
.center_y(Fill)
.into()
}

215
rustclock/src/sports.rs Normal file
View file

@ -0,0 +1,215 @@
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum Sport {
NBA,
NFL,
MLB,
}
#[derive(Debug)]
pub struct Game {
pub sport: Sport,
pub team1: String,
pub team2: String,
pub score1: String,
pub score2: String,
pub period: u8,
}
impl Game {
pub fn new(
sport: Sport,
team1: &str,
team2: &str,
score1: &str,
score2: &str,
period: u8,
) -> Self {
Game {
sport,
team1: team1.to_string(),
team2: team2.to_string(),
score1: score1.to_string(),
score2: score2.to_string(),
period,
}
}
pub fn update(&mut self, score1: &str, score2: &str, period: u8) {
self.score1 = score1.to_string();
self.score2 = score2.to_string();
self.period = period;
}
}
pub fn update_mlb() -> Vec<Game> {
let date = chrono::Local::now().format("%Y-%m-%d").to_string();
let mlb_url = format!(
"https://statsapi.mlb.com/api/v1/schedule?sportId=1&date={}",
date
);
let mlb_games = ureq::get(&mlb_url)
.header("User-Agent", "deathclock-app/1.0")
.call()
.unwrap()
.into_body()
.read_to_vec()
.unwrap();
let mlb_json: serde_json::Value = serde_json::from_slice(&mlb_games).unwrap();
let games = mlb_json["dates"][0]["games"].as_array().unwrap();
let mut mlb_games_vec = Vec::new();
for game in games {
let home_team = game["teams"]["away"]["team"]["name"].as_str().unwrap();
let away_team = game["teams"]["home"]["team"]["name"].as_str().unwrap();
let home_score = game["teams"]["away"]["score"]
.as_str()
.unwrap_or_else(|| "0");
let away_score = game["teams"]["home"]["score"]
.as_str()
.unwrap_or_else(|| "0");
let period = game["status"]["period"]
.as_str()
.unwrap_or_default()
.parse::<u8>()
.unwrap_or_default();
let mut mlb_game_struct = Game::new(
Sport::MLB,
home_team,
away_team,
home_score,
away_score,
period,
);
mlb_game_struct.update(&home_score, &away_score, period);
mlb_games_vec.push(mlb_game_struct);
dbg!("Home Team: {}", home_team);
dbg!("Away Team: {}", away_team);
dbg!("Home Score: {}", home_score);
dbg!("Away Score: {}", away_score);
dbg!("Period: {}", period);
}
mlb_games_vec
}
pub fn update_nba() -> Vec<Game> {
let nba_games =
ureq::get("https://cdn.nba.com/static/json/liveData/scoreboard/todaysScoreboard_00.json")
.header("User-Agent", "deathclock-app/1.0")
.call()
.unwrap()
.into_body()
.read_to_vec()
.unwrap();
let json: serde_json::Value = serde_json::from_slice(&nba_games).unwrap();
let games = json["scoreboard"]["games"].as_array().unwrap();
let mut updated_games: Vec<Game> = Vec::new();
for game in games {
let game_id = game["gameId"].as_str().unwrap();
let home_team = game["homeTeam"]["teamName"].as_str().unwrap();
let away_team = game["awayTeam"]["teamName"].as_str().unwrap();
let home_score = game["homeTeam"]["score"].as_u64().unwrap().to_string();
let away_score = game["awayTeam"]["score"].as_u64().unwrap().to_string();
let period = game["period"].as_u64().unwrap() as u8;
let mut game = Game::new(
Sport::NBA,
home_team,
away_team,
&home_score,
&away_score,
period,
);
game.update(&home_score, &away_score, period);
updated_games.push(game);
dbg!("Game ID: {}", game_id);
dbg!("Home Team: {}", home_team);
dbg!("Away Team: {}", away_team);
dbg!("Home Score: {}", home_score);
dbg!("Away Score: {}", away_score);
dbg!("Period: {}", period);
}
updated_games
}
pub fn get_mlb_logos() -> HashMap<String, Vec<u8>> {
let json = std::fs::read_to_string("src/files/mlb_logos.json").unwrap();
let parsed_json: serde_json::Value = serde_json::from_str(&json).unwrap();
let teams = parsed_json.as_array().unwrap();
let mut logos_map = std::collections::HashMap::new();
for team in teams {
let team_name = team["name"].as_str().unwrap();
let logo_url = team["logo"].as_str().unwrap();
logos_map.insert(team_name.to_string(), logo_url.to_string());
}
let mut logos_svg_map = std::collections::HashMap::new();
for (team_name, logo_url) in logos_map.iter() {
let response = ureq::get(logo_url)
.header("User-Agent", "deathclock-app/0.1")
.call()
.unwrap();
let image_data = response.into_body().read_to_vec().unwrap();
logos_svg_map.insert(team_name.to_string(), image_data);
}
logos_svg_map
}
pub fn get_nba_logos() -> HashMap<String, Vec<u8>> {
let json = std::fs::read_to_string("src/files/nba_logos.json").unwrap();
let parsed_json: serde_json::Value = serde_json::from_str(&json).unwrap();
let teams = parsed_json.as_array().unwrap();
let mut logos_map = std::collections::HashMap::new();
for team in teams {
let team_name = team["name"].as_str().unwrap();
let logo_url = team["logo"].as_str().unwrap();
logos_map.insert(team_name.to_string(), logo_url.to_string());
}
let mut nba_svg_map = std::collections::HashMap::new();
for (team_name, logo_url) in logos_map.iter() {
let response = ureq::get(logo_url)
.header("User-Agent", "deathclock-app/0.1")
.call()
.unwrap();
dbg!("Response {}", response.status());
let image_data = response.into_body().read_to_vec().unwrap();
nba_svg_map.insert(team_name.to_string(), image_data);
dbg!("Downloaded logo for {}", team_name);
}
nba_svg_map
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_nba_logos() {
let nba_logos = get_nba_logos();
assert!(!nba_logos.is_empty());
}
#[test]
fn test_get_mlb_logos() {
let mlb_logos = get_mlb_logos();
assert!(!mlb_logos.is_empty());
}
#[test]
fn test_get_nba_scores() {
let nba_scores = update_nba();
assert!(!nba_scores.is_empty());
}
#[test]
fn test_get_mlb_scores() {
let mlb_scores = update_mlb();
assert!(!mlb_scores.is_empty());
}
}

16
rustclock/src/weather.rs Normal file
View file

@ -0,0 +1,16 @@
use iced::widget::image::Handle;
use ureq;
pub fn get_weather() -> Option<Handle> {
let image = ureq::get("https://v2.wttr.in/Sacramento.png?u0")
.header("User-Agent", "deathclock-app/1.0")
.call()
.unwrap()
.into_body()
.read_to_vec()
.unwrap();
let handle = Some(Handle::from_bytes(image));
dbg!("updating weather");
handle
}

View file

@ -9,8 +9,37 @@ pkgs.mkShell {
python313Packages.ninja
python313Packages.numpy
bun
unstable.rustc
unstable.cargo
unstable.rust-analyzer
unstable.rustfmt
pkgs.pkg-config
pkgs.openssl
pkgs.libxcb
pkgs.pkg-config
pkgs.openssl
pkgs.alsa-lib
pkgs.libxcb
pkgs.wayland
pkgs.libxkbcommon
pkgs.fontconfig
pkgs.freetype
pkgs.mesa
pkgs.libGL
pkgs.glib
pkgs.vulkan-loader
pkgs.vulkan-headers
pkgs.clippy
];
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
pkgs.wayland
pkgs.libxkbcommon
pkgs.mesa
pkgs.glib
pkgs.vulkan-loader
];
shellHook = ''
source .venv/bin/activate
# export PATH="${pkgs.bun}/bin:$PATH"

View file

@ -100,14 +100,4 @@ class News:
if len(all_entries) > 30:
all_entries = random.sample(all_entries, 30)
try:
async with aiofiles.open("news.txt", "w") as f:
print("Writing news to file...")
for entry in all_entries:
await f.write(
f"[{entry['publish_date']}] {entry['source']}: {entry['title']}\n"
)
except Exception as e:
print(f"Error writing to news.txt: {e}")
return all_entries