#!/usr/bin/env python3 # stolat, cron-based birthday notifier # Please run once per day. # License: GPLv3 or later import toml, os, importlib from datetime import datetime scripts = [] def get_current_path() -> str: return os.path.dirname(os.path.abspath(__file__)) def load_config() -> dict: # Try to get configuration from current_path/list.toml try: path = get_current_path() config = toml.load(path + "/list.toml") except: print("Sorry! It seems like I can't access list.toml. Is it in the script's working directory?") quit(-1) return config def calc_age(birth_date: str) -> int: date_as_datetime = datetime.strptime(birth_date, "%Y-%m-%d") delta = datetime.today() - date_as_datetime return round(delta.days / 365.25) def notify_user(birthday: dict, user: dict, actions: dict, days_till_birthday: int): # print(f"Got {birthday}, {user} and actions {actions}.") for channel in user['channels']: for notify_action in actions: if notify_action['id'] == channel: # User wants to receive a notification through current notify_action try: module = importlib.import_module("scripts." + notify_action['name_of_script'][:notify_action['name_of_script'].rfind(".py")]) func = getattr(module, notify_action['startup_function']) func("v1", user['user'], birthday['name'], calc_age(birthday['date']), days_till_birthday, birthday['date']) # v1-formatted function call except Exception as e: print(f"Error: failed executing {notify_action['startup_function']}() for {user['user']} ({birthday['name']}'s {calc_age(birthday['date'])}th birthday) :(\n" f"Exception: {e}.") def iterate_birthdays(config: dict): badly_formatted_birthdays = 0 today = datetime.today() for birthday in config['birthdays']: try: name = birthday['name'] birthdate_as_str = birthday['date'] birthdate = datetime.strptime(birthdate_as_str, "%Y-%m-%d") to_notify = birthday['to_notify'] except Exception as e: badly_formatted_birthdays += 1 continue for user in config['preferences']: birthdate_this_year = birthdate.replace(year=today.year) # Safeguard against no additional reminders if not user['additional_reminders']: user['additional_reminders'] = [] # Consider the birthday as additional reminder # This way we check the list once. if (birthdate_this_year - today).days + 1 in user['additional_reminders']: notify_user(birthday, user, config['actions'], (birthdate_this_year - today).days + 1) if badly_formatted_birthdays > 0: print(f"Warning: found {badly_formatted_birthdays} incorrectly formatted birth dates.\n" f" They haven't been checked for possible birthdays. Please use ISO 8601.") def main(): config = load_config() iterate_birthdays(config) if __name__ == "__main__": main()