mirror of
https://github.com/Death916/deathclock.git
synced 2026-04-10 03:04:40 -07:00
commit
4101bf7587
12 changed files with 6751 additions and 11 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -13,3 +13,9 @@ chromedriver
|
|||
*.flox
|
||||
*.direnv
|
||||
.envrc
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
*/target
|
||||
|
||||
|
|
|
|||
6022
rustclock/Cargo.lock
generated
Normal file
6022
rustclock/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
14
rustclock/Cargo.toml
Normal file
14
rustclock/Cargo.toml
Normal 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"
|
||||
32
rustclock/src/files/mlb_logos.json
Normal file
32
rustclock/src/files/mlb_logos.json
Normal 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"}
|
||||
]
|
||||
32
rustclock/src/files/nba_logos.json
Normal file
32
rustclock/src/files/nba_logos.json
Normal 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
167
rustclock/src/main.rs
Normal 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
60
rustclock/src/news.rs
Normal 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
157
rustclock/src/panes.rs
Normal 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
215
rustclock/src/sports.rs
Normal 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
16
rustclock/src/weather.rs
Normal 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
|
||||
}
|
||||
31
shell.nix
31
shell.nix
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue