chore: commit files

This commit is contained in:
2025-04-18 23:50:45 +02:00
parent 7bccf78148
commit b3efee4ea0
4 changed files with 141 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# User configuration
list.toml
# Python garbage
__pycache__
scripts/__pycache__

30
list.example.toml Normal file
View File

@@ -0,0 +1,30 @@
preferences = [
# Bob prefers to be notified about Alice's birthday
# with SMS, while Alice prefers XMPP.
{user="bob@localhost", channels=[2]},
# Moreover, Alice prefers to be notified a week and a day before
# in addition to the standard notification on the day of the birthday.
{user="alice@localhost", channels=[1], "additional_reminders"=[1, 7]}
]
birthdays = [
# Alice will get notified about Bob's birthday, and Bob about Alice's.
{name="Bob", date="2000-01-01", to_notify=["alice@localhost"]},
{name="Alice", date="2000-01-23", to_notify=["bob@localhost"]}
]
actions = [
# Predefined, trusted scripts to run.
# Scripts are going to be searched for inside of the "scripts" directory.
{id=0, name="print information", name_of_script="sample_script.py", startup_function="print_v1_data"},
# These are not implemented!
{id=1, name="send XMPP message", name_of_script="dummy.py", startup_function="send_message"},
{id=2, name="send SMS", name_of_script="dummy.py", startup_function="send_sms"}
]

86
notify.py Normal file
View File

@@ -0,0 +1,86 @@
#!/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, timedelta
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()

19
scripts/sample_script.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python3
# Implement your functions here...
def print_v1_data(*args):
if args[0] == "v1":
string = (f"Called with API version {args[0]}\n"
f"to send to user {args[1]}\n"
f"about {args[2]}\n"
f"who turns {args[3]}\n"
f"in {args[4]} days\n"
f"with birthday being {args[5]}.\n")
print(string)
else:
print("Called with a wrong API version!")
if __name__ == "__main__":
print("This script isn't meant be ran interactively!")