mirror of
https://github.com/Death916/deathclock.git
synced 2026-04-10 03:04:40 -07:00
base of reflex re write. centered clock and weather
This commit is contained in:
parent
aba3a7b641
commit
3bec50f40e
24 changed files with 1563 additions and 6838 deletions
4
deathclock/.gitignore
vendored
4
deathclock/.gitignore
vendored
|
|
@ -1,4 +0,0 @@
|
|||
*.png
|
||||
*.txt
|
||||
chromedriver
|
||||
*.pyc
|
||||
|
|
@ -1 +0,0 @@
|
|||
__version__ = '0.1.0'
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#alarm.py
|
||||
# alarm component for dash app
|
||||
|
||||
from dash import html, dcc
|
||||
import datetime
|
||||
import time
|
||||
|
||||
class Alarm:
|
||||
def __init__(self):
|
||||
self.current_time = datetime.datetime.now()
|
||||
self.alarms = []
|
||||
self.alarm_times = []
|
||||
|
||||
|
||||
def add_alarm(self, alarm_time,current_time):
|
||||
self.alarms.append(alarm_time)
|
||||
|
||||
self.current_time = current_time
|
||||
|
||||
print(f"Alarm set for {alarm_time}")
|
||||
|
||||
def check_alarm(self):
|
||||
current_time = datetime.datetime.now()
|
||||
if current_time.time() in self.alarm_times:
|
||||
print("Alarm triggered!")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import datetime
|
||||
from dash import html, dcc, Input, Output, State
|
||||
import alarm
|
||||
|
||||
class AlarmModule:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.alarm_obj = self.get_alarm_object()
|
||||
self._alarm_time = None
|
||||
self.setup_callbacks()
|
||||
|
||||
def get_alarm_object(self):
|
||||
return alarm.Alarm()
|
||||
|
||||
def setup_callbacks(self):
|
||||
@self.app.callback(
|
||||
Output('time-input', 'style'),
|
||||
Input('clock-display', 'n_clicks'),
|
||||
State('time-input', 'style')
|
||||
)
|
||||
def toggle_time_input(n_clicks, current_style):
|
||||
if n_clicks and n_clicks % 2 == 1:
|
||||
return {'display': 'block', 'margin': '0 auto'}
|
||||
return {'display': 'none', 'margin': '0 auto'}
|
||||
|
||||
@self.app.callback(
|
||||
Output('output-selected-time', 'children'),
|
||||
Input('time-input', 'value')
|
||||
)
|
||||
def process_selected_time(time_value):
|
||||
if time_value:
|
||||
self._alarm_time = time_value
|
||||
self.alarm_obj.add_alarm(time_value, datetime.datetime.now())
|
||||
return f'Alarm time: {time_value}'
|
||||
return 'No time selected yet.'
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
from dash import Dash, html, dcc
|
||||
from weather_module import WeatherModule
|
||||
from news_module import NewsModule
|
||||
from scores_module import ScoresModule
|
||||
from alarm_module import AlarmModule
|
||||
from clock_module import ClockModule
|
||||
|
||||
def create_app():
|
||||
app = Dash(__name__)
|
||||
|
||||
app.layout = html.Div([
|
||||
html.H1(id='clock-display', style={'textAlign': 'center', 'cursor': 'pointer'}),
|
||||
dcc.Input(id='time-input', type='time', style={'display': 'none', 'margin': '0 auto'}),
|
||||
html.Div(id='output-selected-time', style={'textAlign': 'center', 'marginBottom': '20px'}),
|
||||
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.H2("NBA Scores"),
|
||||
html.Div(id='nba-scores-display', className='score-container')
|
||||
], id='nba-container', style={'flex': '1'}),
|
||||
|
||||
html.Div(id='weather-display', style={"display": "flex", "justify-content": "center", "margin-bottom":"20px", 'flex':'1'}),
|
||||
|
||||
html.Div([
|
||||
html.H2("MLB Scores"),
|
||||
html.Div(id='mlb-scores-display', className='score-container',style={'column-count': '2', 'column-gap': '10px'})
|
||||
], id='mlb-container', style={'flex': '1'}),
|
||||
|
||||
], id='main-content-container', style={"display": "flex", "gap": "20px", 'flex-wrap': 'wrap'}),
|
||||
|
||||
html.Div(id='news-ticker'),
|
||||
|
||||
dcc.Interval(id='clock-interval', interval=60000, n_intervals=0),
|
||||
dcc.Interval(id='weather-interval', interval=550000, n_intervals=0),
|
||||
dcc.Interval(id='news-interval', interval=300000, n_intervals=0),
|
||||
dcc.Interval(id='nba-interval', interval=300000, n_intervals=0),
|
||||
dcc.Interval(id='mlb-interval', interval=300000, n_intervals=0)
|
||||
])
|
||||
|
||||
ClockModule(app)
|
||||
WeatherModule(app)
|
||||
NewsModule(app)
|
||||
scores_module = ScoresModule(app)
|
||||
scores_module.setup_mlb_callbacks()
|
||||
alarm_module = AlarmModule(app)
|
||||
|
||||
def check_alarms():
|
||||
trigg = alarm_module.alarm_obj.check_alarm()
|
||||
if trigg:
|
||||
print("ALARM TRIGGERED!")
|
||||
|
||||
check_alarms()
|
||||
return app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
app.run_server(debug=False, host='0.0.0.0', port=8050)
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
body {
|
||||
background-color: #1a1a2e;
|
||||
color: #e6e6fa;
|
||||
font-family: 'Arial', sans-serif;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
overflow-x: hidden;
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Main Content Container (Scores and Weather) */
|
||||
#main-content-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#nba-container,
|
||||
#mlb-container {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
/* Individual Score Containers */
|
||||
.score-container {
|
||||
width: 100%;
|
||||
padding: 0; /* Remove padding from score container */
|
||||
margin: 0; /* remove margin from score container */
|
||||
}
|
||||
|
||||
#mlb-scores-display {
|
||||
column-count: 1;
|
||||
}
|
||||
|
||||
.score-box {
|
||||
background-color: #232338;
|
||||
padding: 2px 5px; /* Reduced padding on score boxes */
|
||||
margin-bottom: 2px; /* Reduced margin on score boxes */
|
||||
border-radius: 6px;
|
||||
border: 1px solid #4a4a82;
|
||||
display: flex; /* Use flexbox for inner alignment */
|
||||
flex-direction: column; /* Stack the game-score and game-period vertically */
|
||||
justify-content: center; /* Center vertically */
|
||||
}
|
||||
|
||||
.game-score {
|
||||
font-size: 0.9em;
|
||||
color: #e6e6fa;
|
||||
text-align: center;
|
||||
margin: 2px 0; /* Add a small top/bottom margin */
|
||||
}
|
||||
|
||||
.game-period {
|
||||
font-size: 0.8em;
|
||||
color: #b39ddb;
|
||||
text-align: center;
|
||||
margin: 2px 0; /* Add a small top/bottom margin */
|
||||
}
|
||||
|
||||
/* Weather Styles */
|
||||
#weather-display {
|
||||
background-color: #232338;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin: 10px auto;
|
||||
max-width: 100%;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid #4a4a82;
|
||||
transition: all 0.3s ease;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#weather-display:hover {
|
||||
box-shadow: 0 6px 8px rgba(179, 157, 219, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
#weather-display img {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* News Ticker Styles */
|
||||
.ticker {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
background-color: #232338;
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #4a4a82;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
display: inline-block;
|
||||
padding-right: 30px;
|
||||
color: #d1c4e9;
|
||||
animation: ticker linear infinite;
|
||||
}
|
||||
|
||||
@keyframes ticker {
|
||||
0% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Media Queries */
|
||||
@media (min-width: 750px) {
|
||||
/* Landscape layout */
|
||||
body {
|
||||
padding: 20px;
|
||||
font-size: 18px;
|
||||
max-width: 1500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#main-content-container {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
#mlb-scores-display {
|
||||
column-count: 2;
|
||||
}
|
||||
|
||||
.score-container {
|
||||
padding: 0; /* Remove padding from score container */
|
||||
margin: 0; /* remove margin from score container */
|
||||
}
|
||||
|
||||
.score-box {
|
||||
padding: 2px 5px; /* Reduced padding on score boxes */
|
||||
margin-bottom: 2px; /* Reduced margin on score boxes */
|
||||
}
|
||||
|
||||
#weather-display {
|
||||
padding: 20px;
|
||||
margin: 0;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.ticker {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
padding-right: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
/* Phone screens */
|
||||
body {
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
transform-origin: top left;
|
||||
transform: scale(0.6);
|
||||
width: 166.66%;
|
||||
margin-left: -33.33%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
#main-content-container {
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
#nba-container,
|
||||
#mlb-container {
|
||||
min-width: 0;
|
||||
}
|
||||
.score-container {
|
||||
padding: 0; /* Remove padding from score container */
|
||||
margin: 0; /* remove margin from score container */
|
||||
}
|
||||
.score-box {
|
||||
padding: 2px 5px; /* Reduced padding on score boxes */
|
||||
margin-bottom: 2px; /* Reduced margin on score boxes */
|
||||
}
|
||||
|
||||
.game-score {
|
||||
font-size: 0.8em;
|
||||
margin: 2px 0; /* Add a small top/bottom margin */
|
||||
}
|
||||
|
||||
.game-period {
|
||||
font-size: 0.7em;
|
||||
margin: 2px 0; /* Add a small top/bottom margin */
|
||||
}
|
||||
|
||||
.ticker {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -1,15 +0,0 @@
|
|||
from time import strftime, localtime
|
||||
from dash import Input, Output
|
||||
|
||||
class ClockModule:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.setup_callbacks()
|
||||
|
||||
def setup_callbacks(self):
|
||||
@self.app.callback(
|
||||
Output('clock-display', 'children'),
|
||||
Input('clock-interval', 'n_intervals')
|
||||
)
|
||||
def update_time(n):
|
||||
return strftime("%B %d, %I:%M %p", localtime())
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
"""Welcome to Reflex! This file outlines the steps to create a basic app."""
|
||||
|
||||
import reflex as rx
|
||||
from datetime import datetime, timezone
|
||||
import asyncio
|
||||
|
||||
from rxconfig import config
|
||||
from utils.weather import Weather
|
||||
from utils.scores import NBAScores, mlbScores
|
||||
from utils.news import News
|
||||
from utils.alarm import Alarm
|
||||
|
||||
|
||||
|
||||
class State(rx.State):
|
||||
"""The app state."""
|
||||
|
||||
|
||||
|
||||
def index() -> rx.Component:
|
||||
|
||||
return rx.container(
|
||||
rx.center(
|
||||
rx.vstack(
|
||||
rx.button(
|
||||
rx.moment(interval=1000, format="HH:mm:ss"),
|
||||
font_size="4xl",
|
||||
font_weight="bold",
|
||||
color="white",
|
||||
background_color="#6f42c1",
|
||||
),
|
||||
rx.box(
|
||||
rx.image(
|
||||
src=Weather().get_weather_screenshot(),
|
||||
alt="Weather",
|
||||
width="100%",
|
||||
height="auto",
|
||||
background_color="white",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app = rx.App()
|
||||
app.add_page(index)
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
#deathclock
|
||||
import datetime
|
||||
import time
|
||||
import sys
|
||||
from PySide6.QtGui import QGuiApplication
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QTimer, QObject, Signal, Slot, Property
|
||||
from time import strftime, localtime
|
||||
import requests
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
import os
|
||||
import news
|
||||
import weather
|
||||
|
||||
|
||||
|
||||
class clock():
|
||||
def update_time(self):
|
||||
curr_time = strftime("%B %d, %I:%M %p", localtime())
|
||||
|
||||
engine.rootObjects()[0].setProperty('currTime', curr_time)
|
||||
|
||||
|
||||
return curr_time
|
||||
|
||||
|
||||
def time_and_date():
|
||||
print(time.asctime())
|
||||
|
||||
def alarm():
|
||||
alarm_time = input("what time should the alarm be set?")
|
||||
|
||||
|
||||
def ring():
|
||||
pass
|
||||
|
||||
|
||||
"""
|
||||
class gui():
|
||||
def handleTouchAreaPressed(self, signal):
|
||||
# Implement your desired behavior when the left area is pressed
|
||||
print("here touch area")
|
||||
leftTouchAreaMouse = engine.rootObjects()[0].findChild("leftTouchAreaMouse")
|
||||
leftTouchAreaMouse.connect(b"touchAreaPressed", self.handleTouchAreaPressed)
|
||||
print("Left area pressed!")
|
||||
"""
|
||||
def main():
|
||||
#gui_obj = gui()
|
||||
|
||||
app = QGuiApplication(sys.argv)
|
||||
global engine
|
||||
engine = QQmlApplicationEngine()
|
||||
engine.quit.connect(app.quit)
|
||||
|
||||
engine.load( 'main.qml')
|
||||
# create instance of weather class
|
||||
weather_obj = weather.Weather()
|
||||
weather_obj.weatherUpdated.connect(lambda weather_map_path: engine.rootContext().setContextProperty("weatherMapPath", weather_map_path))
|
||||
|
||||
# set timer for weather map
|
||||
weatherTimer = QTimer()
|
||||
weatherTimer.setInterval(300000) # 10 minutes
|
||||
weatherTimer.timeout.connect(weather_obj.download_sacramento_weather_map(engine))
|
||||
weather_obj.download_sacramento_weather_map(engine)
|
||||
|
||||
|
||||
|
||||
|
||||
weatherTimer.start()
|
||||
|
||||
|
||||
# create instance of clock class
|
||||
timeupdate = clock()
|
||||
|
||||
# start timer for clock
|
||||
timer = QTimer()
|
||||
timer.setInterval(100) # msecs 100 = 1/10th sec
|
||||
timer.timeout.connect(timeupdate.update_time)
|
||||
timer.start()
|
||||
|
||||
# create instance of news class
|
||||
news_obj = news.news()
|
||||
news_ticker = news_obj.get_news()
|
||||
#print(news_obj._news_dict)
|
||||
#print(news_ticker)
|
||||
news_context = engine.rootContext()
|
||||
news_context.setContextProperty("news", str(news_ticker))
|
||||
|
||||
#start timer for news
|
||||
news_timer = QTimer()
|
||||
news_timer.timeout.connect(news_obj.get_news)
|
||||
news_timer.setInterval(300000) # 10 minutes #changed to 5 mins
|
||||
|
||||
news_timer.start()
|
||||
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
import feedparser
|
||||
import asyncio
|
||||
import aiofiles
|
||||
import random
|
||||
from time import localtime, strftime
|
||||
import socket
|
||||
import aiohttp
|
||||
|
||||
def print_time():
|
||||
print(strftime("%B %d, %I:%M %p", localtime()))
|
||||
|
||||
class News:
|
||||
def __init__(self):
|
||||
self._news_dict = {}
|
||||
self._news_dict_length = 0
|
||||
socket.setdefaulttimeout(10) # Set default timeout for socket operations
|
||||
|
||||
async def _fetch_feed(self, session, feed):
|
||||
"""Fetches and parses a single feed asynchronously."""
|
||||
max_entries = 10 # Maximum number of entries to fetch from each feed
|
||||
|
||||
try:
|
||||
# Add timeout to the request
|
||||
timeout = aiohttp.ClientTimeout(total=5)
|
||||
async with session.get(feed, timeout=timeout) as response:
|
||||
if response.status != 200:
|
||||
print(f"Skip feed {feed}: status {response.status}")
|
||||
return []
|
||||
|
||||
text = await response.text()
|
||||
d = feedparser.parse(text)
|
||||
|
||||
if hasattr(d, 'status') and d.status != 200:
|
||||
print(f"Skip feed {feed}: status {d.status}")
|
||||
return []
|
||||
|
||||
feed_entries = []
|
||||
# Limit the number of entries parsed
|
||||
for i, post in enumerate(d.entries):
|
||||
if i >= max_entries:
|
||||
break # Stop parsing if we've reached the limit
|
||||
|
||||
feed_entries.append({
|
||||
'title': post.title,
|
||||
'source': d.feed.title if hasattr(d.feed, 'title') else 'Unknown',
|
||||
'publish_date': post.published if hasattr(post, 'published') else '',
|
||||
'summary': post.summary if hasattr(post, 'summary') else ''
|
||||
})
|
||||
|
||||
print(f"Added {len(feed_entries)} entries from {feed}")
|
||||
return feed_entries
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
print(f"Error processing feed {feed}: {e}")
|
||||
return []
|
||||
except Exception as e:
|
||||
print(f"Error processing feed {feed}: {e}")
|
||||
return []
|
||||
|
||||
async def get_news(self):
|
||||
print_time()
|
||||
feeds = []
|
||||
self._news_dict = {}
|
||||
self._news_dict_length = 0
|
||||
|
||||
try:
|
||||
async with aiofiles.open("feeds.txt", "r") as f:
|
||||
async for line in f:
|
||||
feeds.append(line.strip())
|
||||
except Exception as e:
|
||||
print(f"Error reading feeds.txt: {e}")
|
||||
return {}
|
||||
|
||||
# Limit the number of feeds to process at once
|
||||
if len(feeds) > 10:
|
||||
feeds = random.sample(feeds, 10)
|
||||
|
||||
print("Getting news entries...")
|
||||
timeout = aiohttp.ClientTimeout(total=15)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
tasks = [self._fetch_feed(session, feed) for feed in feeds]
|
||||
all_feed_entries_list = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
all_entries = []
|
||||
for result in all_feed_entries_list:
|
||||
if isinstance(result, list) and result:
|
||||
all_entries.extend(result)
|
||||
|
||||
if not all_entries:
|
||||
print("No entries collected")
|
||||
return {}
|
||||
|
||||
if len(all_entries) > 30:
|
||||
all_entries = random.sample(all_entries, 30)
|
||||
|
||||
for entry in all_entries:
|
||||
self._news_dict[entry['title']] = entry
|
||||
|
||||
try:
|
||||
async with aiofiles.open("news.txt", "w") as f:
|
||||
print("Writing news to file...")
|
||||
for entry in self._news_dict.values():
|
||||
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 self._news_dict
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import datetime
|
||||
import asyncio
|
||||
from dash import html, Input, Output, callback, no_update
|
||||
from dash.exceptions import PreventUpdate
|
||||
from news import News
|
||||
|
||||
class NewsModule:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.news_obj = self.get_news_object()
|
||||
self._last_news_update = datetime.datetime(2000, 1, 1)
|
||||
self._cached_news = self.create_loading_message() # Initial loading message
|
||||
self._initial_run = True
|
||||
self.setup_callbacks()
|
||||
|
||||
def get_news_object(self):
|
||||
return News()
|
||||
|
||||
def create_loading_message(self):
|
||||
return html.Div("Loading...")
|
||||
|
||||
def setup_callbacks(self):
|
||||
@self.app.callback(
|
||||
Output('news-ticker', 'children'),
|
||||
Input('news-interval', 'n_intervals')
|
||||
)
|
||||
def update_news(n):
|
||||
if n is None:
|
||||
return self._cached_news
|
||||
|
||||
current_time = datetime.datetime.now()
|
||||
time_since_update = (current_time - self._last_news_update).total_seconds()
|
||||
|
||||
# Only update if it's been more than 5 minutes or it's the initial run
|
||||
if time_since_update < 300 and not self._initial_run:
|
||||
return self._cached_news
|
||||
|
||||
try:
|
||||
print("UPDATING NEWS...")
|
||||
# Create a new event loop for this request
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
headlines_dict = loop.run_until_complete(self.news_obj.get_news())
|
||||
loop.close()
|
||||
|
||||
if not headlines_dict:
|
||||
return html.Div("No news available at this time.", className="ticker")
|
||||
|
||||
combined_items = " | ".join([f"{data['source']}: {headline}"
|
||||
for headline, data in headlines_dict.items()])
|
||||
text_px = len(combined_items) * 8
|
||||
scroll_speed = 75
|
||||
duration = max(text_px / scroll_speed, 20)
|
||||
ticker_style = {"animationDuration": f"{duration}s"}
|
||||
|
||||
self._cached_news = html.Div(
|
||||
html.Span(combined_items, className="news-item", style=ticker_style),
|
||||
className='ticker'
|
||||
)
|
||||
|
||||
self._last_news_update = current_time
|
||||
self._initial_run = False
|
||||
return self._cached_news
|
||||
except Exception as e:
|
||||
print(f"Error updating news: {e}")
|
||||
return html.Div("No news available.")
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
from nba_api.live.nba.endpoints import scoreboard
|
||||
from datetime import datetime, timedelta
|
||||
import statsapi
|
||||
|
||||
class NBAScores:
|
||||
def __init__(self):
|
||||
self._scores = []
|
||||
|
||||
def get_scores(self):
|
||||
try:
|
||||
# Get scoreboard data
|
||||
board = scoreboard.ScoreBoard()
|
||||
data = board.get_dict()
|
||||
|
||||
# Check if we have a valid scoreboard response
|
||||
if 'scoreboard' not in data:
|
||||
print("No scoreboard data found")
|
||||
return []
|
||||
|
||||
games = data['scoreboard'].get('games', [])
|
||||
if not games:
|
||||
print("No games found in scoreboard")
|
||||
return []
|
||||
|
||||
scores_list = []
|
||||
for game in games:
|
||||
try:
|
||||
game_data = {
|
||||
'home_team': game['homeTeam']['teamTricode'],
|
||||
'home_score': game['homeTeam']['score'],
|
||||
'away_team': game['awayTeam']['teamTricode'],
|
||||
'away_score': game['awayTeam']['score'],
|
||||
'period': game['period'],
|
||||
'status': game['gameStatusText']
|
||||
}
|
||||
scores_list.append(game_data)
|
||||
except KeyError as e:
|
||||
print(f"Error processing game data: {e}")
|
||||
continue
|
||||
|
||||
self._scores = scores_list
|
||||
return self._scores
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fetching scores: {e}")
|
||||
return []
|
||||
|
||||
class mlbScores:
|
||||
def __init__(self):
|
||||
self._scores = []
|
||||
self._games = []
|
||||
|
||||
|
||||
def get_games(self):
|
||||
try:
|
||||
# Get MLB games data
|
||||
games = statsapi.schedule()
|
||||
self._games = games
|
||||
return self._games
|
||||
except Exception as e:
|
||||
print(f"Error fetching MLB games: {e}")
|
||||
return []
|
||||
|
||||
def get_scores(self):
|
||||
games = self.get_games()
|
||||
scores_list = []
|
||||
if not games:
|
||||
print("No mlb games found")
|
||||
return []
|
||||
for game in games:
|
||||
try:
|
||||
game_data = {
|
||||
'home_team': game['home_name'],
|
||||
'home_score': game['home_score'],
|
||||
'away_team': game['away_name'],
|
||||
'away_score': game['away_score'],
|
||||
'status': game['status']
|
||||
}
|
||||
scores_list.append(game_data)
|
||||
except KeyError as e:
|
||||
print(f"Error processing game data: {e}")
|
||||
continue
|
||||
return scores_list # RETURN THE LIST
|
||||
|
||||
if __name__ == "__main__":
|
||||
scores = NBAScores()
|
||||
results = scores.get_scores()
|
||||
|
||||
print("\nNBA Scores:")
|
||||
if results:
|
||||
for game in results:
|
||||
print(f"{game['away_team']} {game['away_score']} @ "
|
||||
f"{game['home_team']} {game['home_score']} "
|
||||
f"(Status: {game['status']})")
|
||||
else:
|
||||
print("No games available")
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
from dash import html, Input, Output
|
||||
from scores import NBAScores # Import NBAScores class
|
||||
from scores import mlbScores # Import mlbScores class
|
||||
|
||||
class ScoresModule:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.scores_obj = self.get_scores_object()
|
||||
self.mlb_scores_obj = self.get_mlb_scores_object()
|
||||
self.setup_callbacks()
|
||||
|
||||
|
||||
def get_scores_object(self):
|
||||
return NBAScores()
|
||||
|
||||
def setup_callbacks(self):
|
||||
@self.app.callback(
|
||||
Output('nba-scores-display', 'children'),
|
||||
Input('nba-interval', 'n_intervals')
|
||||
)
|
||||
def update_scores(n):
|
||||
try:
|
||||
games = self.scores_obj.get_scores()
|
||||
if not games:
|
||||
print("no nba games found")
|
||||
return html.Div("No games available", className='text-center')
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Span(f"{game['away_team']} {game['away_score']}"),
|
||||
html.Span(" @ "),
|
||||
html.Span(f"{game['home_team']} {game['home_score']}")
|
||||
], className='game-score'),
|
||||
html.Div(f"Period: {game['period']}", className='game-period')
|
||||
], className='score-box')
|
||||
for game in games
|
||||
], className='score-container')
|
||||
except Exception as e:
|
||||
return html.Div("Scores unavailable")
|
||||
def get_mlb_scores_object(self):
|
||||
return mlbScores()
|
||||
|
||||
def setup_mlb_callbacks(self):
|
||||
@self.app.callback(
|
||||
Output('mlb-scores-display', 'children'),
|
||||
Input('mlb-interval', 'n_intervals')
|
||||
)
|
||||
def update_mlb_scores(n):
|
||||
try:
|
||||
games = self.mlb_scores_obj.get_scores()
|
||||
print("Updating MLB Scores")
|
||||
|
||||
if not games:
|
||||
print("no mlb games found")
|
||||
return html.Div("No games available", className='text-center')
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Span(f"{game['away_team']} {game['away_score']}"),
|
||||
html.Span(" @ "),
|
||||
html.Span(f"{game['home_team']} {game['home_score']}")
|
||||
], className='game-score'),
|
||||
html.Div(f"Status: {game['status']}", className='game-period')
|
||||
], className='score-box')
|
||||
for game in games
|
||||
], className='score-container')
|
||||
except Exception as e:
|
||||
print(f"Error updating MLB Scores: {e}")
|
||||
return html.Div("Scores unavailable")
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import os
|
||||
import subprocess
|
||||
import datetime
|
||||
|
||||
class Weather:
|
||||
def __init__(self):
|
||||
# Get the directory where this script (weather.py) is located
|
||||
self.script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Construct the absolute path to the 'assets' directory in the same directory as the script
|
||||
self.assets_dir = os.path.join(self.script_dir, 'assets')
|
||||
|
||||
# Ensure the assets directory exists
|
||||
if not os.path.exists(self.assets_dir):
|
||||
os.makedirs(self.assets_dir)
|
||||
|
||||
def delete_old_screenshots(self):
|
||||
"""
|
||||
Deletes all PNG files in the 'assets' directory that start with 'sacramento_weather_'.
|
||||
"""
|
||||
for filename in os.listdir(self.assets_dir):
|
||||
if filename.startswith("sacramento_weather_"):
|
||||
os.remove(os.path.join(self.assets_dir, filename))
|
||||
|
||||
def get_weather_screenshot(self):
|
||||
"""
|
||||
Fetches weather information for Sacramento from wttr.in using curl and saves it as a PNG.
|
||||
Returns the path to the saved image.
|
||||
"""
|
||||
try:
|
||||
# Create a timestamp for the filename
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
screenshot_filename = f"sacramento_weather_{timestamp}.png"
|
||||
screenshot_path = os.path.join(self.assets_dir, screenshot_filename) # save to the proper location
|
||||
|
||||
# Use curl to get the weather data from wttr.in and save it as a PNG.
|
||||
# add the scale #2 to make the png larger
|
||||
curl_command = [
|
||||
"curl",
|
||||
"-s", # Silent mode
|
||||
"v2.wttr.in/Sacramento.png?0pq&scale=.5", # Fetch weather data for Sacramento
|
||||
"-o",
|
||||
screenshot_path,
|
||||
]
|
||||
self.delete_old_screenshots()
|
||||
subprocess.run(curl_command, check=True)
|
||||
|
||||
return screenshot_path
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error fetching weather data: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
return None
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import datetime
|
||||
import os
|
||||
from dash import html, Input, Output
|
||||
from weather import Weather # Import Weather class
|
||||
|
||||
class WeatherModule:
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.weather_obj = self.get_weather_object()
|
||||
self.setup_callbacks()
|
||||
|
||||
def get_weather_object(self):
|
||||
return Weather()
|
||||
|
||||
def setup_callbacks(self):
|
||||
@self.app.callback(
|
||||
Output('weather-display', 'children'),
|
||||
Input('weather-interval', 'n_intervals')
|
||||
)
|
||||
def update_weather(n):
|
||||
try:
|
||||
print("UPDATING WEATHER...")
|
||||
screenshot_path = self.weather_obj.get_weather_screenshot()
|
||||
image_name = os.path.basename(screenshot_path)
|
||||
return html.Div(
|
||||
[
|
||||
html.H2("Sacramento Weather"),
|
||||
html.Img(
|
||||
src=self.app.get_asset_url(image_name + f"?v={datetime.datetime.now().timestamp()}"),
|
||||
style={"width": "100%", "display": "block", "image-rendering": "crisp-edges"}
|
||||
),
|
||||
],
|
||||
style={"width": "600px", "margin": "0 auto", "border": "1px solid black"}
|
||||
)
|
||||
except Exception as e:
|
||||
return html.Div(f"Weather update error: {str(e)}")
|
||||
Loading…
Add table
Add a link
Reference in a new issue