signal-bot/signal_bot/day_log.py
Krzysztof kuhy Rudnicki 77795ee6f8
Some checks are pending
CI / check (push) Waiting to run
CI / web (push) Waiting to run
Today's food and workout logs, read from where diet_guard and screen_locker keep them
Two commands, food_today and workout_today, each printing the current
Warsaw day out of a log another program owns: every meal with its time,
calories and source plus the day's total and macros; every workout with
every field it carries. Three workout fields are withheld (a reservation
phone number, a screenshot path, the sync record id), and the food budget
is never printed -- diet_guard keeps that number on the machine on purpose.

The spellings the group uses carry a surname, so they are custom aliases in
the state directory; aliases.json ships only plain words. The suite points
both logs at absent temp files so it can never read the operator's own,
and the e2e run writes one invented entry of each under today's date.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 22:07:46 +02:00

58 lines
2.3 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
"""One day out of a day-keyed JSON log another program keeps.
diet_guard and screen_locker both write the same shape: an object whose keys
are ``YYYY-MM-DD`` and whose values are lists of entry objects. Neither file
belongs to this bot -- they are read where they are, never copied and never
written -- so where they are is the operator's to say, through one
environment variable per log, with the path the two programs use by default.
The entries are HMAC-signed by their owners. This module does not check the
signatures, because the keys are those programs' secrets: what it reads is
what it shows, and a forged line in the operator's own home directory is a
problem the bot is not the place to solve.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any
class DayLogError(RuntimeError):
"""The log cannot be read at all, as opposed to having nothing for a day."""
def path_from_env(variable: str, default: str) -> Path:
"""Where the log is: the variable if it is set, the program's default otherwise."""
explicit = os.environ.get(variable, "").strip()
return Path(explicit).expanduser() if explicit else Path(default).expanduser()
def read_day(path: Path, day: str) -> list[dict[str, Any]]:
"""Every entry the log holds for ``day``, in the order the file gives them.
A day the log has no key for is an empty list, not an error: an empty
day is the normal case before breakfast.
Raises:
DayLogError: The file is missing, unreadable, not JSON, or not the
object-of-days shape -- anything that makes "nothing today"
a claim this module cannot make.
"""
try:
document = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
msg = f"{path} cannot be read: {error}"
raise DayLogError(msg) from error
if not isinstance(document, dict):
msg = f"{path} is not an object of days"
raise DayLogError(msg)
entries = document.get(day, [])
if not isinstance(entries, list):
msg = f"{path}: {day} is not a list of entries"
raise DayLogError(msg)
return [entry for entry in entries if isinstance(entry, dict)]