I was looking at the manual process I use when scheduling tasks that I log in obsidian and wondered whether I could automate it. In a perfect world, I'd be able to automate the process and send the tasks to Akiflow. But this is not a perfect world and after some digging, I had to fall back on seeing if I could at least leverage Apple reminders.
I'm not a developer but I have written a number scripts in python. But that was some time ago so instead of starting from scratch I decided to turn to one of my new best friends, Chatgpt. FYI, my wife is jealous of my new "friend" :).
Enter the prompt,
Prompt #1
create python code that will parse a directory of markdown files for incomplete checklist items with the hashtag #task
This returned a good start but I needed more. I needed a way to identify that the script found and touched the task. Let's continue.
Prompt #2
modify each line that is found and append this text ">scheduled"
This returned new code to do exactly what I asked but I needed to do more. Let’s continue to the next prompt.
Prompt #3
and mark the checklist as complete and for each checklist item found create an apple reminder and make the path to the file that the checklist item was found as a link in the note text of the apple reminder
Here's what chatgpt returned.
"Because executing AppleScript from Python can vary depending on your macOS security settings, I'll show an example using the `osascript` command, which is a utility to run AppleScript commands from the terminal (or from a Python script). Please note, creating a shortcut that adds a reminder and then calling that shortcut might be a more modern approach but requires setup outside of this script."
I was pleasantly surprised that chatgpt recommended that I use a more modern approach and leverage a shortcut. But I wanted a straightforward solution.
One more thing I need chatgpt to do with the script. Here's the last prompt.
Prompt #4
now make it so the apple reminder is scheduled for today
Here's the python code chatgpt returned. Five minutes if that and AI produced much better code than I could have written in the same amount of time.
import os
import re
import subprocess
import urllib.parse
from datetime import datetime
def create_reminder(task, file_path):
# Get today's date in the format YYYY-MM-DD
today_date = datetime.now().strftime("%Y-%m-%d")
# Convert file path to URL format
file_url = urllib.parse.quote(file_path, safe="")
full_url = f"file://{file_url}"
# AppleScript to create a reminder scheduled for today with a link to the file in the note
script = f'''tell application "Reminders"
set newReminder to make new reminder
set name of newReminder to "{task}"
set body of newReminder to "File link: {full_url}"
set remind me date of newReminder to date "{today_date}"
save
end tell'''
subprocess.run(['osascript', '-e', script])
def modify_and_remind(directory):
task_pattern = re.compile(r'- \[ \] (.+?#tasks)')
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
with open(file_path, 'r', encoding='utf-8') as f:
contents = f.readlines()
modified = False
for i, line in enumerate(contents):
match = task_pattern.search(line)
if match:
# Mark the task as complete and append ">scheduled"
contents[i] = task_pattern.sub(r'- [x] \1 >scheduled', line)
modified = True
# Create an Apple Reminder for each task scheduled for today with a link to the file
create_reminder(match.group(1), file_path)
if modified:
with open(file_path, 'w', encoding='utf-8') as f:
f.writelines(contents)
# Replace this with your directory path
directory_path = '/path/to/your/markdown/files'
modify_and_remind(directory_path)
With most of the script complete, I began to make a few more modifications without the assistance of chatgpt.
I wanted to replace the direct link to the file using the "file://" format and change it so I could use the obsidian link format.
To do this I add a couple of variables, "vault_name" and "obsidian_url". This should be self-explanatory. Here's what those changes look like.
# Replace this with your directory path to obsidian vault
directory_path = '/Users/antoneheyward/Documents/Obsidian/The Lab'
# Name of the obsidian vault. Assumed to be the last directory in the directory_path variable.
vault_name = directory_path.split('/')[-1]
# This takes the default obsidian link format and adds the obsidian vault.
obsidian_url = 'obsidian://open?vault=' + vault_name.replace(" ","%20") + '&file='
modify_and_remind(directory_path)
Lastly, I had an issue with file path urls when there was a space introduced in the file path, an example being that my test obsidian vault is named "The Lab". To fix this I used the "pathlib" module, specifically:
from pathlib import Path
I also tweaked the script variable used to create the Apple reminder to use the new file path variable and added the hashtag 'obsidian' into the note of the reminder.
set body of newReminder to "#obsidian\n'{new_filepath}'"
With all that done, here's the entire script code. You can copy it and have fun but I make no guarantees.
import os
import re
import subprocess
import urllib.parse
from datetime import datetime
from pathlib import Path
def create_reminder(task, file_path):
# Get today's date in the format YYYY-MM-DD
today_date = datetime.now().strftime("%m-%d-%Y")
# Convert file path to URL format
file_url = urllib.parse.quote(file_path, safe="")
data_folder = Path(file_path)
full_url = f"file://{data_folder}"
# Remove #task and replace with #obsidian if file found in Obsidian directory
task = task.replace("#task", "")
new_filepath = obsidian_url + full_url.split(vault_name)[1].replace("/","%2F").replace(" ","%20")
print(new_filepath)
# AppleScript to create a reminder scheduled for today with a link to the file in the note
script = f'''tell application "Reminders"
set newReminder to make new reminder
set name of newReminder to "{task}"
set body of newReminder to "#obsidian\n'{new_filepath}'"
set remind me date of newReminder to date "{today_date}"
save
end tell'''
#set body of newReminder to "File link: '{full_url}'"
subprocess.run(['osascript', '-e', script])
def modify_and_remind(directory):
task_pattern = re.compile(r'- \[ \] (.+?#task)')
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
with open(file_path, 'r', encoding='utf-8') as f:
contents = f.readlines()
modified = False
for i, line in enumerate(contents):
match = task_pattern.search(line)
if match:
# Mark the task as complete and append ">scheduled"
contents[i] = task_pattern.sub(r'- [x] \1 >scheduled', line)
modified = True
# Create an Apple Reminder for each task scheduled for today with a link to the file
create_reminder(match.group(1), file_path)
if modified:
with open(file_path, 'w', encoding='utf-8') as f:
f.writelines(contents)
# Replace this with your directory path to obsidian vault
directory_path = '/Users/antoneheyward/Documents/Obsidian/The Lab'
# Name of the obsidian vault. Assumed to be the last directory in the directory_path variable.
vault_name = directory_path.split('/')[-1]
# This takes the default obsidian link format and adds the obsidian vault.
obsidian_url = 'obsidian://open?vault=' + vault_name.replace(" ","%20") + '&file='
modify_and_remind(directory_path)
With this script, you can now parse your Obsidian vault for all checklist items that include the hashtag 'task' that has not been previously scheduled or has '>scheduled' in the line.
You could automate this or manually run it if you find it useful. This might not be the best way to achieve scheduling tasks in obsidian but it shows that with a little creative thinking, you can do just about anything your heart desires.