New job: daily tasks from a calendar

This commit is contained in:
Dejvino 2026-02-01 20:20:29 +01:00
parent b9e96b5945
commit 618b1641ff
5 changed files with 159 additions and 1 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
__pycache__ __pycache__
calendar_url.txt

View File

@ -14,6 +14,23 @@ pip install python-escpos[all] --user
pip install chess pip install chess
``` ```
### Tasks from Google Calendar
```
pip install requests icalendar recurring-ical-events pytz
```
You need to specify the calendar URL by pasting it into `calendar_url.txt` in this project directory.
> To find the secret iCal URL for your Google Calendar, follow these steps:
>
> Open Google Calendar in your web browser.
> In the top right, click the Settings (gear icon) > Settings.
> On the left sidebar, under "Settings for my calendars", click the name of the calendar you want to use.
> Scroll down to the "Integrate calendar" section.
> Look for the field "Secret address in iCal format".
> Copy this URL (it should end in .ics).
## Config ## Config

108
jobs/tasks.py Normal file
View File

@ -0,0 +1,108 @@
import datetime
import os.path
from .base import Job
# Wrap imports to prevent crashing if dependencies are missing
try:
import requests
import icalendar
import recurring_ical_events
import pytz
HAS_ICAL_DEPS = True
except ImportError:
HAS_ICAL_DEPS = False
class TasksJob(Job):
def get_name(self):
return "UKOLY DNE"
def configure(self):
print("\n--- Configure Calendar URL ---")
print("To find your Google Calendar URL:")
print("1. Go to Settings > Select Calendar > Integrate calendar")
print("2. Copy 'Secret address in iCal format'")
print("-" * 30)
print("Enter the public or secret address in iCal format (.ics).")
print("Example: https://calendar.google.com/calendar/ical/.../basic.ics")
url = input("URL: ").strip()
if url:
with open('calendar_url.txt', 'w') as f:
f.write(url)
print("URL saved.")
def print_body(self, p):
if not HAS_ICAL_DEPS:
print("Error: Missing iCal Libraries.")
p.text("Error: Missing iCal Libraries.\n")
p.text("Run: pip install requests icalendar recurring-ical-events pytz\n")
return
if not os.path.exists('calendar_url.txt'):
print("Error: Calendar URL not configured.")
p.text("Error: Calendar URL not configured.\n")
p.text("Run configuration in TUI first.\n")
return
with open('calendar_url.txt', 'r') as f:
url = f.read().strip()
try:
# 1. Fetch the .ics file
# Timeout is important so the printer doesn't hang forever
response = requests.get(url, timeout=30)
response.raise_for_status()
# 2. Parse Calendar
cal = icalendar.Calendar.from_ical(response.content)
# 3. Calculate Time Range (Today)
now = datetime.datetime.now().astimezone()
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
end_of_day = now.replace(hour=23, minute=59, second=59, microsecond=0)
# 4. Expand Recurring Events
# This library handles RRULEs (e.g. "Every Monday") automatically
events = recurring_ical_events.of(cal).between(start_of_day, end_of_day)
# 5. Sort by start time
# Helper to get sortable time
def get_start_time(e):
dt = e.get('DTSTART').dt
# If it's a date object (all day), convert to datetime for sorting
if not isinstance(dt, datetime.datetime):
return datetime.datetime.combine(dt, datetime.time.min).replace(tzinfo=now.tzinfo)
return dt
events.sort(key=get_start_time)
if not events:
p.text("Zadne ukoly pro dnesni den.\n")
else:
for event in events:
summary = str(event.get('SUMMARY', '(bez nazvu)'))
dtstart = event.get('DTSTART').dt
# Format time
if not isinstance(dtstart, datetime.datetime):
time_str = "Cely den"
else:
# Convert to local system time for display
# (If dtstart is timezone aware, astimezone(None) converts to local)
local_dt = dtstart.astimezone(now.tzinfo)
time_str = local_dt.strftime('%H:%M')
# Print Entry
p.set(bold=True)
p.text(f"{time_str} [_]")
p.set(bold=False)
p.text(f" {summary}\n")
location = event.get('LOCATION')
if location:
p.text(f" @ {str(location)}\n")
p.text("-" * 32 + "\n")
except Exception as e:
print(f"Error: {e}")
p.text(f"Error: {e}\n")

View File

@ -11,6 +11,7 @@ from jobs.joke import JokeJob
from jobs.maze_multitarget import MazeMultitargetJob from jobs.maze_multitarget import MazeMultitargetJob
from jobs.flush import FlushJob from jobs.flush import FlushJob
from jobs.word_search import WordSearchJob from jobs.word_search import WordSearchJob
from jobs.tasks import TasksJob
# ========================================== # ==========================================
# CONFIGURATION # CONFIGURATION
@ -60,6 +61,7 @@ JOBS = [
MathHomeworkJob(), MathHomeworkJob(),
UnitConversionJob(), UnitConversionJob(),
ChessPuzzleJob(), ChessPuzzleJob(),
TasksJob(),
MazeJob(), MazeJob(),
DivisionCipherJob(), DivisionCipherJob(),
DecimalDivisionJob(), DecimalDivisionJob(),

30
print_tasks.py Normal file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
import time
from print_server import get_printer
from jobs.tasks import TasksJob
def main():
print("Initializing printer...")
p = get_printer()
if not p:
print("Failed to connect to printer.")
return
print("Fetching and printing tasks...")
job = TasksJob()
try:
# Run the job
job.run(p)
print("Done.")
except Exception as e:
print(f"Error during print job: {e}")
finally:
# Ensure connection is closed cleanly
if hasattr(p, 'close'):
time.sleep(0.5)
p.close()
if __name__ == "__main__":
print(" == Tasks Printer ==")
main()