Compare commits
5 Commits
4575584b9d
...
042df5efc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
042df5efc2 | ||
|
|
6f5d744628 | ||
|
|
618b1641ff | ||
|
|
b9e96b5945 | ||
|
|
6d16d40b7f |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
calendar_url.txt
|
||||||
17
README.md
17
README.md
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from jobs.czech_words import CZECH_WORDS
|
|||||||
|
|
||||||
class DivisionCipherJob(Job):
|
class DivisionCipherJob(Job):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.secret = "TAJENKA"
|
self.user_phrase = None
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "TAJENKA DELENIM"
|
return "TAJENKA DELENIM"
|
||||||
@ -13,21 +13,20 @@ class DivisionCipherJob(Job):
|
|||||||
def configure(self):
|
def configure(self):
|
||||||
print("\n--- Configure Division Cipher ---")
|
print("\n--- Configure Division Cipher ---")
|
||||||
phrase = input("Enter secret phrase (default: Random): ").strip().upper()
|
phrase = input("Enter secret phrase (default: Random): ").strip().upper()
|
||||||
|
self.user_phrase = phrase if phrase else None
|
||||||
raw_secret = phrase if phrase else random.choice(CZECH_WORDS)
|
|
||||||
|
def print_body(self, p):
|
||||||
|
raw_secret = self.user_phrase if self.user_phrase else random.choice(CZECH_WORDS)
|
||||||
|
|
||||||
# Remove accents to ensure mapping to A-Z works
|
# Remove accents to ensure mapping to A-Z works
|
||||||
nfkd_form = unicodedata.normalize('NFKD', raw_secret)
|
nfkd_form = unicodedata.normalize('NFKD', raw_secret)
|
||||||
only_ascii = "".join([c for c in nfkd_form if not unicodedata.combining(c)])
|
only_ascii = "".join([c for c in nfkd_form if not unicodedata.combining(c)])
|
||||||
|
|
||||||
# Keep only A-Z
|
|
||||||
self.secret = "".join([c for c in only_ascii.upper() if 'A' <= c <= 'Z'])
|
|
||||||
|
|
||||||
if not self.secret:
|
|
||||||
self.secret = "TAJENKA"
|
|
||||||
|
|
||||||
def print_body(self, p):
|
# Keep only A-Z
|
||||||
secret = self.secret
|
secret = "".join([c for c in only_ascii.upper() if 'A' <= c <= 'Z'])
|
||||||
|
|
||||||
|
if not secret:
|
||||||
|
secret = "TAJENKA"
|
||||||
|
|
||||||
p.text("Vylusti tajenku!\n")
|
p.text("Vylusti tajenku!\n")
|
||||||
p.text("Vysledek deleni je poradi pismena\n")
|
p.text("Vysledek deleni je poradi pismena\n")
|
||||||
|
|||||||
131
jobs/tasks.py
Normal file
131
jobs/tasks.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
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 __init__(self):
|
||||||
|
self.days_to_print = 1
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return "UKOLY DNE"
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
if not os.path.exists('calendar_url.txt'):
|
||||||
|
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.")
|
||||||
|
else:
|
||||||
|
print("\nCalendar URL is already configured (delete calendar_url.txt to reset).")
|
||||||
|
|
||||||
|
print("-" * 30)
|
||||||
|
try:
|
||||||
|
d_input = input(f"Number of days to print [{self.days_to_print}]: ").strip()
|
||||||
|
if d_input:
|
||||||
|
self.days_to_print = int(d_input)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
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. Loop over days
|
||||||
|
base_now = datetime.datetime.now().astimezone()
|
||||||
|
|
||||||
|
for i in range(self.days_to_print):
|
||||||
|
p.cut()
|
||||||
|
|
||||||
|
target_date = base_now + datetime.timedelta(days=i)
|
||||||
|
|
||||||
|
# Print Calendar Name
|
||||||
|
cal_name = cal.get('X-WR-CALNAME')
|
||||||
|
if cal_name:
|
||||||
|
p.set(align='center', bold=True)
|
||||||
|
p.text(f"{str(cal_name)}\n")
|
||||||
|
days_cz = ["Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle"]
|
||||||
|
p.text(f"{days_cz[target_date.weekday()]} {target_date.strftime('%d.%m.%Y')}\n")
|
||||||
|
p.set(align='left', bold=False)
|
||||||
|
p.text("-" * 32 + "\n")
|
||||||
|
|
||||||
|
start_of_day = target_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
end_of_day = target_date.replace(hour=23, minute=59, second=59, microsecond=0)
|
||||||
|
|
||||||
|
# 4. Expand Recurring Events
|
||||||
|
events = recurring_ical_events.of(cal).between(start_of_day, end_of_day)
|
||||||
|
|
||||||
|
# 5. Sort by start time
|
||||||
|
def get_start_time(e):
|
||||||
|
dt = e.get('DTSTART').dt
|
||||||
|
if not isinstance(dt, datetime.datetime):
|
||||||
|
return datetime.datetime.combine(dt, datetime.time.min).replace(tzinfo=base_now.tzinfo)
|
||||||
|
return dt
|
||||||
|
|
||||||
|
events.sort(key=get_start_time)
|
||||||
|
|
||||||
|
if not events:
|
||||||
|
p.text("Zadne ukoly.\n")
|
||||||
|
else:
|
||||||
|
for event in events:
|
||||||
|
summary = str(event.get('SUMMARY', '(bez nazvu)'))
|
||||||
|
dtstart = event.get('DTSTART').dt
|
||||||
|
|
||||||
|
if not isinstance(dtstart, datetime.datetime):
|
||||||
|
time_str = "Cely den"
|
||||||
|
else:
|
||||||
|
local_dt = dtstart.astimezone(base_now.tzinfo)
|
||||||
|
time_str = local_dt.strftime('%H:%M')
|
||||||
|
|
||||||
|
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")
|
||||||
@ -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(),
|
||||||
@ -115,6 +117,8 @@ def run_tui():
|
|||||||
if copies > 1:
|
if copies > 1:
|
||||||
print(f" Printing copy {i + 1}...")
|
print(f" Printing copy {i + 1}...")
|
||||||
job.run(p)
|
job.run(p)
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
# If using Dummy, print the output to console to verify
|
# If using Dummy, print the output to console to verify
|
||||||
if isinstance(p, Dummy):
|
if isinstance(p, Dummy):
|
||||||
|
|||||||
36
print_tasks.py
Normal file
36
print_tasks.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
from print_server import get_printer
|
||||||
|
from jobs.tasks import TasksJob
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Print daily tasks schedule.")
|
||||||
|
parser.add_argument("--days", type=int, default=1, help="Number of days to print (default: 1)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print("Initializing printer...")
|
||||||
|
p = get_printer()
|
||||||
|
if not p:
|
||||||
|
print("Failed to connect to printer.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Fetching and printing tasks for {args.days} day(s)...")
|
||||||
|
job = TasksJob()
|
||||||
|
job.days_to_print = args.days
|
||||||
|
|
||||||
|
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()
|
||||||
Loading…
Reference in New Issue
Block a user