Merge branch 'modules' of https://github.com/death916/deathclock into modules

This commit is contained in:
Death916 2025-03-08 04:52:30 -08:00
commit 765a02e8d4
4 changed files with 59 additions and 36 deletions

View file

@ -7,12 +7,12 @@ from clock_module import ClockModule
def create_app(): def create_app():
app = Dash(__name__) app = Dash(__name__)
app.layout = html.Div([ app.layout = html.Div([
html.H1(id='clock-display', style={'textAlign': 'center', 'cursor': 'pointer'}), html.H1(id='clock-display', style={'textAlign': 'center', 'cursor': 'pointer'}),
dcc.Input(id='time-input', type='time', style={'display': 'none', 'margin': '0 auto'}), 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(id='output-selected-time', style={'textAlign': 'center', 'marginBottom': '20px'}),
html.Div([ html.Div([
html.Div([ html.Div([
html.Div([ html.Div([
@ -22,26 +22,27 @@ def create_app():
html.Div(id='weather-display') html.Div(id='weather-display')
], id='scores-weather-container'), ], id='scores-weather-container'),
]), ]),
html.Div(id='news-ticker'), html.Div(id='news-ticker'),
dcc.Interval(id='clock-interval', interval=60000, n_intervals=0), dcc.Interval(id='clock-interval', interval=60000, n_intervals=0),
dcc.Interval(id='weather-interval', interval=150000, n_intervals=0), dcc.Interval(id='weather-interval', interval=150000, n_intervals=0),
dcc.Interval(id='news-interval', interval=300000, 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='nba-interval', interval=300000, n_intervals=0)
]) ])
ClockModule(app) ClockModule(app)
WeatherModule(app) WeatherModule(app)
NewsModule(app) NewsModule(app)
ScoresModule(app) ScoresModule(app)
alarm_module = AlarmModule(app) alarm_module = AlarmModule(app)
def check_alarms(): def check_alarms():
trigg = alarm_module.alarm_obj.check_alarm() trigg = alarm_module.alarm_obj.check_alarm()
if trigg: if trigg:
print("ALARM TRIGGERED!") print("ALARM TRIGGERED!")
check_alarms() check_alarms()
return app return app
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -13,37 +13,43 @@ class News:
def __init__(self): def __init__(self):
self._news_dict = {} self._news_dict = {}
self._news_dict_length = 0 self._news_dict_length = 0
socket.setdefaulttimeout(10) # Set default timeout for socket operations socket.setdefaulttimeout(10) # Set default timeout for socket operations
async def _fetch_feed(self, session, feed): async def _fetch_feed(self, session, feed):
"""Fetches and parses a single feed asynchronously.""" """Fetches and parses a single feed asynchronously."""
max_entries = 10 # Maximum number of entries to fetch from each feed max_entries = 10 # Maximum number of entries to fetch from each feed
try: try:
async with session.get(feed) as response: # Add timeout to the request
timeout = aiohttp.ClientTimeout(total=5)
async with session.get(feed, timeout=timeout) as response:
if response.status != 200: if response.status != 200:
print(f"Skip feed {feed}: status {response.status}") print(f"Skip feed {feed}: status {response.status}")
return [] return []
text = await response.text() text = await response.text()
d = feedparser.parse(text) d = feedparser.parse(text)
if hasattr(d, 'status') and d.status != 200: if hasattr(d, 'status') and d.status != 200:
print(f"Skip feed {feed}: status {d.status}") print(f"Skip feed {feed}: status {d.status}")
return [] return []
feed_entries = [] feed_entries = []
# Limit the number of entries parsed # Limit the number of entries parsed
for i, post in enumerate(d.entries): for i, post in enumerate(d.entries):
if i >= max_entries: if i >= max_entries:
break # Stop parsing if we've reached the limit break # Stop parsing if we've reached the limit
feed_entries.append({ feed_entries.append({
'title': post.title, 'title': post.title,
'source': d.feed.title if hasattr(d.feed, 'title') else 'Unknown', 'source': d.feed.title if hasattr(d.feed, 'title') else 'Unknown',
'publish_date': post.published if hasattr(post, 'published') else '', 'publish_date': post.published if hasattr(post, 'published') else '',
'summary': post.summary if hasattr(post, 'summary') else '' 'summary': post.summary if hasattr(post, 'summary') else ''
}) })
print(f"Added {len(feed_entries)} entries from {feed}") print(f"Added {len(feed_entries)} entries from {feed}")
return feed_entries return feed_entries
except aiohttp.ClientError as e: except aiohttp.ClientError as e:
print(f"Error processing feed {feed}: {e}") print(f"Error processing feed {feed}: {e}")
return [] return []
@ -56,7 +62,7 @@ class News:
feeds = [] feeds = []
self._news_dict = {} self._news_dict = {}
self._news_dict_length = 0 self._news_dict_length = 0
try: try:
async with aiofiles.open("feeds.txt", "r") as f: async with aiofiles.open("feeds.txt", "r") as f:
async for line in f: async for line in f:
@ -64,28 +70,32 @@ class News:
except Exception as e: except Exception as e:
print(f"Error reading feeds.txt: {e}") print(f"Error reading feeds.txt: {e}")
return {} return {}
# Limit the number of feeds to process at once
if len(feeds) > 10:
feeds = random.sample(feeds, 10)
print("Getting news entries...") print("Getting news entries...")
async with aiohttp.ClientSession() as session: timeout = aiohttp.ClientTimeout(total=15)
async with aiohttp.ClientSession(timeout=timeout) as session:
tasks = [self._fetch_feed(session, feed) for feed in feeds] tasks = [self._fetch_feed(session, feed) for feed in feeds]
all_feed_entries_list = await asyncio.gather(*tasks) all_feed_entries_list = await asyncio.gather(*tasks, return_exceptions=True)
all_entries = [] all_entries = []
for feed_entries in all_feed_entries_list: for result in all_feed_entries_list:
if feed_entries: if isinstance(result, list) and result:
#Now just add the entries, because we are already limited all_entries.extend(result)
all_entries.extend(feed_entries)
if not all_entries: if not all_entries:
print("No entries collected") print("No entries collected")
return {} return {}
if len(all_entries) > 30: if len(all_entries) > 30:
all_entries = random.sample(all_entries, 30) all_entries = random.sample(all_entries, 30)
for entry in all_entries: for entry in all_entries:
self._news_dict[entry['title']] = entry self._news_dict[entry['title']] = entry
try: try:
async with aiofiles.open("news.txt", "w") as f: async with aiofiles.open("news.txt", "w") as f:
print("Writing news to file...") print("Writing news to file...")
@ -93,5 +103,5 @@ class News:
await f.write(f"[{entry['publish_date']}] {entry['source']}: {entry['title']}\n") await f.write(f"[{entry['publish_date']}] {entry['source']}: {entry['title']}\n")
except Exception as e: except Exception as e:
print(f"Error writing to news.txt: {e}") print(f"Error writing to news.txt: {e}")
return self._news_dict return self._news_dict

View file

@ -9,16 +9,16 @@ class NewsModule:
self.app = app self.app = app
self.news_obj = self.get_news_object() self.news_obj = self.get_news_object()
self._last_news_update = datetime.datetime(2000, 1, 1) self._last_news_update = datetime.datetime(2000, 1, 1)
self._cached_news = self.create_loading_message() # Initial loading message self._cached_news = self.create_loading_message() # Initial loading message
self._initial_run = True self._initial_run = True
self.setup_callbacks() self.setup_callbacks()
def get_news_object(self): def get_news_object(self):
return News() return News()
def create_loading_message(self): def create_loading_message(self):
return html.Div("Loading...") return html.Div("Loading...")
def setup_callbacks(self): def setup_callbacks(self):
@self.app.callback( @self.app.callback(
Output('news-ticker', 'children'), Output('news-ticker', 'children'),
@ -29,13 +29,25 @@ class NewsModule:
return self._cached_news return self._cached_news
current_time = datetime.datetime.now() 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: try:
print("UPDATING NEWS...") print("UPDATING NEWS...")
# Execute the async function with asyncio.run # Create a new event loop for this request
headlines_dict = asyncio.run(self.news_obj.get_news()) loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
headlines_dict = loop.run_until_complete(self.news_obj.get_news())
loop.close()
combined_items = " | ".join([f"{data['source']}: {headline}" if not headlines_dict:
for headline, data in headlines_dict.items()]) 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 text_px = len(combined_items) * 8
scroll_speed = 75 scroll_speed = 75
duration = max(text_px / scroll_speed, 20) duration = max(text_px / scroll_speed, 20)