diff --git a/.gitignore b/.gitignore index ed8ebf5..91c7794 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__ \ No newline at end of file +__pycache__ +calendar_url.txt \ No newline at end of file diff --git a/README.md b/README.md index 8d838ab..e448508 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,23 @@ pip install python-escpos[all] --user 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 diff --git a/jobs/tasks.py b/jobs/tasks.py new file mode 100644 index 0000000..f5fcbdc --- /dev/null +++ b/jobs/tasks.py @@ -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") diff --git a/print_server.py b/print_server.py index 5dc7179..02f7125 100644 --- a/print_server.py +++ b/print_server.py @@ -11,6 +11,7 @@ from jobs.joke import JokeJob from jobs.maze_multitarget import MazeMultitargetJob from jobs.flush import FlushJob from jobs.word_search import WordSearchJob +from jobs.tasks import TasksJob # ========================================== # CONFIGURATION @@ -60,6 +61,7 @@ JOBS = [ MathHomeworkJob(), UnitConversionJob(), ChessPuzzleJob(), + TasksJob(), MazeJob(), DivisionCipherJob(), DecimalDivisionJob(), diff --git a/print_tasks.py b/print_tasks.py new file mode 100644 index 0000000..4463bfe --- /dev/null +++ b/print_tasks.py @@ -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()