A no-plugin notification system

Hello!

I’ve been using Logseq for a little over a month to organize my life, and it’s honestly life-changing for me. I feel like I’ve found a knowledge management system that actually works with my brain.

One thing I feel that Logseq does poorly is automatically resurfacing information when it becomes relevant. Scheduling tasks is great, but only if you remember to look for them in the queries. I really wanted to schedule something (like a haircut, or a meeting, or something) and have Logseq remind me about it without having to remember to check for upcoming tasks.

Since Logseq stores everything in markdown files, I found this to be pretty trivial with ntfy, a pub-sub notification system.

First you grep your files for upcoming events with this shell script:

notifications.sh:

#!/usr/bin/env bash

YEAR=`date +"%Y"`
MONTH=`date +"%m"`
DAY=`date +"%d"`

cd /home/<user>/logseq
grep "SCHEDULED: <$YEAR-$MONTH-$DAY" * -B 1 -r | python /usr/local/bin/notifications.py $1

and then you pipe the output into python, which curls the data to ntfy, which sends you a notification on your computer/phone/whatever. I do that with this script:

notifications.py

import re
import sys
import requests
import argparse 
from datetime import datetime, time, timedelta

print("Starting notification parsing")

parser = argparse.ArgumentParser(description="This script is used to send notification data to ntfy.sh")
parser.add_argument("--hourly", type=bool, nargs="?", const=True, required=False, help="Run the hourly notifications")
args = parser.parse_args()

sent_notifications = []
def time_diff(time_str: str) -> bool:
    """
    Determines if the given time occurs in the next two hours

    :param time_str: str
    :returns: bool
    """

	current_time = datetime.now()
	input_time = datetime.combine(current_time.date(), datetime.strptime(time_str, '%H:%M').time())
	time_difference = input_time - current_time

    # Is time_str within the next two hours?
	return timedelta(minutes=0) <= time_difference <= timedelta(hours=2)

for line in sys.stdin:
	if line == "--\n" or not line:
		continue
    if "DONE" in line:
            continue
	if "SCHEDULED" not in line:
		title = re.search("(?<=.md-).*$", line)[0].strip()
		continue
	if "SCHEDULED" in line:
		data = re.search("(?<=.md:).*$", line)[0].strip()
		time = re.search("[0-9]{2}:[0-9]{2}", line)
		if time:
			time = time[0]
	if "[#A]" in title:
		priority = "high"
	else:
		priority = "default"
	page = re.search(".*.md", line)[0]

	if args.hourly:
		if time and time_diff(time):
			requests.post("https://ntfy.sh/<ntfy_topic>", data = page, headers = {"Title": title})
			continue
		if not time:
			continue
	else:
		requests.post("https://ntfy.sh/<ntfy_topic>", data = page, headers = {"Title": title})

Then I just schedule the whole thing in the crontab on my server and it’s done!

0 * * * * /usr/local/bin/notifications.sh --hourly
0 8,12,17 * * * /usr/local/bin/notifications.sh

In the shell script above, replace the directory on line 7 with the location of your Logseq directory. In the python script, replace <ntfy_topic> with your ntfy topic.

Running the shell script with the --hourly flag (which I do hourly) notifies you about tasks that are scheduled to happen in the next two hours (so you’ll get two notifications before the task starts). Running the shell script without the flag (which I do three times a day) tells you about all of your tasks scheduled for the day, regardless of the time.

Anyway, this has been very helpful for me, and I’m posting it here in case someone else might find it useful as well.

I gotta say. That’s actually pretty cool. I wonder then whether the .sh script could be translated to powerShell, so that it’s cross-platform (PowerShell is a cross-platform scripting language born out of what was once a Windows-only one). I can fart around with that if you guys want.