diff --git a/alert_clients/alarm_api_client.py b/alert_clients/alarm_api_client.py new file mode 100644 index 0000000..c73bed9 --- /dev/null +++ b/alert_clients/alarm_api_client.py @@ -0,0 +1,102 @@ +import requests +import json + + +class AlarmApiClient: + def __init__(self, base_url: str): + """ + Initialize the Alarm API client. + :param base_url: Base URL of the Alarm API server (e.g., 'http://localhost:8000') + """ + self.base_url = base_url.rstrip("/") + + def _handle_response(self, response: requests.Response) -> dict: + """ + Handle API responses, raising exceptions for errors. + :param response: The HTTP response object. + :return: Parsed JSON data if the response is successful. + """ + try: + response.raise_for_status() + return response.json() + except requests.HTTPError as e: + raise Exception(f"HTTP error {response.status_code}: {response.text}") from e + except json.JSONDecodeError: + raise Exception("Invalid JSON response") + + def get_alarms(self) -> list: + """ + Retrieve all alarms from the API. + :return: List of alarm configurations. + """ + response = requests.get(f"{self.base_url}/") + return self._handle_response(response)["data"] + + def create_alarm(self, alarm_data: dict) -> int: + """ + Create a new alarm. + :param alarm_data: Dictionary containing alarm configuration. + :return: ID of the newly created alarm. + """ + response = requests.post(f"{self.base_url}/", json=alarm_data) + return self._handle_response(response)["data"]["id"] + + def update_alarm(self, alarm_id: int, alarm_data: dict) -> bool: + """ + Update an existing alarm. + :param alarm_id: ID of the alarm to update. + :param alarm_data: Updated alarm configuration. + :return: True if the update was successful. + """ + alarm_data["id"] = alarm_id + response = requests.put(f"{self.base_url}/", json=alarm_data) + return "message" in self._handle_response(response) + + def delete_alarm(self, alarm_id: int) -> bool: + """ + Delete an alarm by its ID. + :param alarm_id: ID of the alarm to delete. + :return: True if the deletion was successful. + """ + response = requests.delete(f"{self.base_url}/", json={"id": alarm_id}) + return "message" in self._handle_response(response) + + +# Example Usage +#if __name__ == "__main__": +# client = AlarmApiClient("http://localhost:8000") +# +# # Create a new alarm +# new_alarm = { +# "name": "Morning Alarm", +# "time": "07:00:00", +# "repeat_rule": { +# "type": "daily" +# }, +# "enabled": True, +# "snooze": { +# "enabled": True, +# "duration": 10, +# "max_count": 3 +# }, +# "metadata": { +# "volume": 80, +# "notes": "Wake up alarm" +# }, +# } +# alarm_id = client.create_alarm(new_alarm) +# print(f"Created alarm with ID: {alarm_id}") +# +# # Get all alarms +# alarms = client.get_alarms() +# print(f"Current alarms: {alarms}") +# +# # Update the alarm +# updated_alarm = new_alarm.copy() +# updated_alarm["time"] = "07:30:00" +# client.update_alarm(alarm_id, updated_alarm) +# print(f"Updated alarm ID {alarm_id}") +# +# # Delete the alarm +# client.delete_alarm(alarm_id) +# print(f"Deleted alarm ID {alarm_id}") diff --git a/alert_clients/alarm_client_cli.py b/alert_clients/alarm_client_cli.py new file mode 100755 index 0000000..b550bd6 --- /dev/null +++ b/alert_clients/alarm_client_cli.py @@ -0,0 +1,151 @@ +#!/usr/bin/python3 + +import argparse +import json +import os +from pathlib import Path +from alarm_api_client import AlarmApiClient + + +CONFIG_FILE = Path.home() / ".alarms.json" + + +def load_config() -> dict: + """Load configuration from the config file.""" + if CONFIG_FILE.exists(): + with CONFIG_FILE.open() as f: + return json.load(f) + return {} + + +def save_config(config: dict) -> None: + """Save configuration to the config file.""" + with CONFIG_FILE.open("w") as f: + json.dump(config, f, indent=4) + print(f"Configuration saved to {CONFIG_FILE}") + + +def main(): + parser = argparse.ArgumentParser(description="CLI for interacting with the Alarm API") + parser.add_argument("--host", type=str, help="Alarm API host URL (overrides saved config)") + parser.add_argument("--save-config", action="store_true", help="Save the provided host URL to the config file") + + subparsers = parser.add_subparsers(title="Commands", dest="command") + + # Create alarm + create_parser = subparsers.add_parser("create", help="Create a new alarm") + create_parser.add_argument("--name", required=False, help="Name of the alarm") + create_parser.add_argument("--time", required=False, help="Alarm time in HH:MM:SS format") + create_parser.add_argument("--repeat-type", choices=["daily", "weekly", "once"], help="Repeat type") + create_parser.add_argument("--days", nargs="*", help="Days of the week for weekly repeat (e.g., monday tuesday)") + create_parser.add_argument("--enabled", type=bool, default=True, help="Enable or disable the alarm") + create_parser.add_argument("--snooze-duration", type=int, default=10, help="Snooze duration in minutes") + create_parser.add_argument("--snooze-count", type=int, default=3, help="Maximum snooze count") + create_parser.add_argument("--volume", type=int, default=80, help="Volume level (0-100)") + create_parser.add_argument("--notes", type=str, default="", help="Notes for the alarm") + create_parser.add_argument("--file", type=str, help="Path to the alarm sound file") + create_parser.add_argument("--from-file", type=str, help="Path to JSON file containing alarm configuration") + + # Get alarms + subparsers.add_parser("list", help="List all alarms") + + # Update alarm + update_parser = subparsers.add_parser("update", help="Update an existing alarm") + update_parser.add_argument("--id", type=int, required=True, help="ID of the alarm to update") + update_parser.add_argument("--name", help="Updated name of the alarm") + update_parser.add_argument("--time", help="Updated time in HH:MM:SS format") + update_parser.add_argument("--repeat-type", choices=["daily", "weekly"], help="Updated repeat type") + update_parser.add_argument("--days", nargs="*", help="Updated days of the week for weekly repeat") + update_parser.add_argument("--enabled", type=bool, help="Enable or disable the alarm") + update_parser.add_argument("--snooze-duration", type=int, help="Updated snooze duration in minutes") + update_parser.add_argument("--snooze-count", type=int, help="Updated maximum snooze count") + update_parser.add_argument("--volume", type=int, help="Updated volume level (0-100)") + update_parser.add_argument("--notes", type=str, help="Updated notes for the alarm") + update_parser.add_argument("--file", type=str, help="Updated path to the alarm sound file") + + # Delete alarm + delete_parser = subparsers.add_parser("delete", help="Delete an alarm") + delete_parser.add_argument("--id", type=int, required=True, help="ID of the alarm to delete") + + args = parser.parse_args() + + # Load saved config + config = load_config() + host = args.host or config.get("host", "http://localhost:8000") + + if args.save_config: + config["host"] = host + save_config(config) + + client = AlarmApiClient(host) + + try: + if args.command == "create": + if args.from_file: + with open(args.from_file, "r") as f: + alarm_data = json.load(f) + print(f"Creating alarm from file: {args.from_file}") + else: + repeat_rule = {"type": args.repeat_type} + if args.repeat_type == "weekly": + repeat_rule["days_of_week"] = args.days or [] + alarm_data = { + "name": args.name, + "time": args.time, + "repeat_rule": repeat_rule, + "enabled": args.enabled, + "snooze": {"enabled": True, "duration": args.snooze_duration, "max_count": args.snooze_count}, + "metadata": {"volume": args.volume, "notes": args.notes}, + } + if args.file: + alarm_data["file_to_play"] = args.file + alarm_id = client.create_alarm(alarm_data) + print(f"Alarm created with ID: {alarm_id}") + + elif args.command == "list": + alarms = client.get_alarms() + if not alarms: + print("No alarms found.") + else: + for alarm in alarms: + print(json.dumps(alarm, indent=4)) + + elif args.command == "update": + alarm_data = {} + if args.name: + alarm_data["name"] = args.name + if args.time: + alarm_data["time"] = args.time + if args.repeat_type: + alarm_data["repeat_rule"] = {"type": args.repeat_type} + if args.repeat_type == "weekly" and args.days: + alarm_data["repeat_rule"]["days_of_week"] = args.days + if args.enabled is not None: + alarm_data["enabled"] = args.enabled + if args.snooze_duration: + alarm_data.setdefault("snooze", {})["duration"] = args.snooze_duration + if args.snooze_count: + alarm_data.setdefault("snooze", {})["max_count"] = args.snooze_count + if args.volume: + alarm_data.setdefault("metadata", {})["volume"] = args.volume + if args.notes: + alarm_data.setdefault("metadata", {})["notes"] = args.notes + if args.file: + alarm_data["file_to_play"] = args.file + + if client.update_alarm(args.id, alarm_data): + print(f"Alarm ID {args.id} updated successfully.") + + elif args.command == "delete": + if client.delete_alarm(args.id): + print(f"Alarm ID {args.id} deleted successfully.") + + else: + parser.print_help() + + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + main()