Skip to content

Instantly share code, notes, and snippets.

@suderman
Last active May 13, 2020 16:53
Show Gist options
  • Save suderman/2e2ff298602967865e2fbe396ec3fbc8 to your computer and use it in GitHub Desktop.
Save suderman/2e2ff298602967865e2fbe396ec3fbc8 to your computer and use it in GitHub Desktop.
Edit DEVONthink text in Drafts (using Pythonista)

Edit DEVONthink text in Drafts (using Pythonista)

Screenshots:

Open these Drafts Actions URLs in iOS

Metadata IN

x-drafts4://x-callback-url/import_action?v=2&tintColor=%5B%0A%20%200.031%2C%0A%20%200.455%2C%0A%20%200.627%0A%5D&shouldConfirm=0&logLevel=1&uuid=E4CDB730-4D86-4387-A639-79405A215627&disposition=0&actionSteps=%5B%0A%20%20%7B%0A%20%20%20%20%22scriptText%22%20%3A%20%22%5C%2F%5C%2F%5C%2F%20Meta%20block%20regex%5Cnlet%20%24re%20%3D%20%5C%2F%5E%28-%7B3%7D%20%2A%5B%5C%5Cn%5C%5Cr%5D%29%3F%28%5B-%20%5C%5Ct%5C%5Cw%5D%2B%3A%5B%20%5C%5Ct%5D%2A.%2B%5B%5C%5Cn%5C%5Cr%5D%29%2B%28-%7B3%7D%7C%5C%5C.%7B3%7D%29%3F%20%2A%5C%2F%5Cndraft.defineTag%28%27re%27%2C%20%24re%29%5Cn%5Cn%5Cn%5C%2F%5C%2F%20Extract%20meta%20block%20as%20object%5Cnlet%20%24meta%20%3D%20%7B%7D%2C%20match%5Cnif%20%28match%20%3D%20draft.content.match%28%24re%29%29%20%7B%5Cn%20%20match%5B0%5D.split%28%27%5C%5Cn%27%29.forEach%28line%20%3D%3E%20%7B%5Cn%20%20%20%20if%20%28line.includes%28%27%3A%27%29%29%20%7B%5Cn%20%20%20%20%20%20let%20key%20%3D%20line.split%28%5C%2F%3A%28.%2B%29%5C%2F%29%5B0%5D.trim%28%29%5Cn%20%20%20%20%20%20let%20val%20%3D%20line.split%28%5C%2F%3A%28.%2B%29%5C%2F%29%5B1%5D.trim%28%29%5Cn%20%20%20%20%20%20%24meta%5Bkey%5D%20%3D%20val%5Cn%20%20%20%20%7D%5Cn%20%20%7D%29%20%20%20%20%5Cn%7D%5Cndraft.defineTag%28%27meta%27%2C%20JSON.stringify%28%24meta%29%29%5Cn%5Cn%5Cn%5C%2F%5C%2F%20Redefine%20draft%20tag%20without%20meta%5Cnlet%20%24draft%20%3D%20draft.content.replace%28%24re%2C%27%27%29.trim%28%29%5Cndraft.defineTag%28%27draft%27%2C%20%24draft%29%5Cn%5Cn%5Cn%5C%2F%5C%2F%20Redefine%20the%20body%20tag%5Cnlet%20%24body%20%3D%20%24draft.split%28%27%5C%5Cn%27%29%5Cn%24body.shift%28%29%2C%20%24body%20%3D%20%24body.join%28%27%5C%5Cn%27%29.trim%28%29%5Cndraft.defineTag%28%27body%27%2C%20%24body%29%3B%5Cn%5Cn%5Cn%5C%2F%5C%2F%20Redefine%20title%20tag%5Cnlet%20%24title%20%3D%20%24draft.split%28%27%5C%5Cn%27%29%5B0%5D.trim%28%29%5Cndraft.defineTag%28%27title%27%2C%20%24title%29%5Cn%5Cn%5Cn%5C%2F%5C%2F%20Define%20document%20title%20from%20meta%20or%20first%20line%5Cnlet%20title%20%3D%20%24title.replace%28%5C%2F%5E%23%7B1%2C6%7D%5C%2F%2C%20%27%27%29.trim%28%29%5Cnlet%20%24document_title%20%3D%20%24meta%5B%27title%27%5D%20%3F%20%24meta%5B%27title%27%5D%20%3A%20%28title%20%3F%20title%20%3A%20Date.now%28%29%29%5Cndraft.defineTag%28%27document_title%27%2C%24document_title%29%5Cn%5Cn%5Cn%5C%2F%5C%2F%20Define%20file%20tags%5Cnlet%20%24file_name%20%3D%20%24document_title%2C%20%24file_ext%3D%27txt%27%5Cnif%20%28%24meta%5B%27file%27%5D%29%20%7B%5Cn%20%20let%20file_array%20%3D%20%24meta%5B%27file%27%5D.split%28%27.%27%29%5Cn%20%20if%20%28file_array.length%20%3E%3D%202%29%20%7B%5Cn%20%20%20%20%24file_ext%20%3D%20file_array.pop%28%29%5Cn%20%20%20%20%24file_name%20%3D%20file_array.join%28%27.%27%29%5Cn%20%20%7D%5Cn%7D%5Cndraft.defineTag%28%27file_name%27%2C%20%24file_name%29%5Cndraft.defineTag%28%27file_ext%27%2C%20%24file_ext%29%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Script%22%0A%20%20%7D%0A%5D&description=To%20be%20included%20in%20other%20actions%20that%20have%20a%20metadata%20block.%20Creates%2Fupdates%20the%20following%20tags%3A%20%0A%0A%5B%5Bmeta%5D%5D%2C%20%5B%5Bdraft%5D%5D%2C%20%5B%5Bbody%5D%5D%2C%20%5B%5Btitle%5D%5D%2C%20%5B%5Bdocument_title%5D%5D%2C%20%5B%5Bfile_name%5D%5D%2C%20%5B%5Bfile_ext%5D%5D%2C%20%20%5B%5Bre%5D%5D&modifiedAt=2017-05-04%2018%3A01%3A09%20%2B0000&name=Metadata%20IN&iconImageName=action_markdown

Metadata OUT

x-drafts4://x-callback-url/import_action?v=2&tintColor=%5B%0A%20%200.031%2C%0A%20%200.455%2C%0A%20%200.627%0A%5D&shouldConfirm=0&logLevel=1&uuid=A4622478-1CA5-41C6-B3B3-E3829AA34C19&disposition=0&actionSteps=%5B%0A%20%20%7B%0A%20%20%20%20%22scriptText%22%20%3A%20%22%5C%2F%5C%2F%20Include%20this%20metadata%20first%5Cnlet%20priority%20%3D%20%5B%27title%27%2C%20%27url%27%5D%5Cn%5Cn%5C%2F%5C%2F%20Exclude%20this%20metadata%5Cnlet%20ignored%20%3D%20%5B%27file%27%5D%5Cn%5Cn%5Cn%5C%2F%5C%2F%20Get%20meta%20tag%20as%20object%2C%20empty%20lines%20array%5Cnlet%20%24meta%20%3D%20JSON.parse%28draft.getTag%28%27meta%27%29%29%2C%5Cn%20%20%20%20%24draft%20%3D%20%5B%5D%2C%20%24draft_filtered%20%3D%20%5B%5D%5Cn%5Cn%5C%2F%5C%2F%20Adds%20new%20line%20with%20meta%20key%20and%20value%5Cnlet%20addLine%20%3D%20%28key%29%20%3D%3E%20%7B%20%5Cn%20%20let%20line%20%3D%20%60%24%7Bkey%7D%3A%20%24%7B%24meta%5Bkey%5D%7D%20%20%60%5Cn%20%20if%20%28Object.keys%28%24meta%29.includes%28key%29%29%20%7B%5Cn%20%20%20%20%24draft.push%28line%29%5Cn%20%20%20%20if%20%28%21ignored.includes%28key%29%29%20%7B%5Cn%20%20%20%20%20%20%24draft_filtered.push%28line%29%5Cn%20%20%20%20%7D%5Cn%20%20%7D%5Cn%20%20delete%20%24meta%5Bkey%5D%5Cn%7D%5Cn%5Cn%5C%2F%5C%2F%20Finish%20lines%20appending%20the%20draft%20to%20the%20end%5Cnlet%20buildDraft%20%3D%20%28lines%29%20%3D%3E%20%7B%5Cn%20%20lines.push%28%27%27%29%5Cn%20%20lines.push%28draft.getTag%28%27draft%27%29%29%5Cn%20%20return%20lines.join%28%27%5C%5Cn%27%29.trim%28%29%5Cn%7D%5Cn%5Cn%5C%2F%5C%2F%20Start%20meta%20with%20specifc%20order%2C%20if%20keys%20exist%5Cnpriority.forEach%28addLine%29%5Cn%5Cn%5C%2F%5C%2F%20Continue%20meta%20with%20any%20keys%20that%20are%20left%5CnObject.keys%28%24meta%29.forEach%28addLine%29%5Cn%5Cn%5C%2F%5C%2F%20Convert%20array%20to%20string%20and%20save%20the%20changes%5Cndraft.defineTag%28%27draft_filtered%27%2C%20buildDraft%28%24draft_filtered%29%29%5Cndraft.content%20%3D%20buildDraft%28%24draft%29%5Cndraft.defineTag%28%27draft%27%2C%20draft.content%29%5Cncommit%28draft%29%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Script%22%0A%20%20%7D%0A%5D&description=Prepend%20the%20draft%20content%20with%20all%20the%20keys%20and%20values%20found%20in%20the%20metadata%20block.%20Updates%20%5B%5Bdraft%5D%5D%20and%20creates%20%5B%5Bdraft_filtered%5D%5D.%20&modifiedAt=2017-05-04%2022%3A21%3A08%20%2B0000&name=Metadata%20OUT&iconImageName=action_markdown

Validate Draft

x-drafts4://x-callback-url/import_action?v=2&tintColor=%5B%0A%20%200.769%2C%0A%20%200.141%2C%0A%20%200.161%0A%5D&shouldConfirm=0&logLevel=1&uuid=EDA50405-E7BF-4383-8693-80058D15F082&disposition=0&actionSteps=%5B%0A%20%20%7B%0A%20%20%20%20%22scriptText%22%20%3A%20%22if%20%28draft.content%20%3D%3D%20%27%27%29%20%7B%5Cn%20%20stopAction%28%29%5Cn%7D%5Cn%5Cnif%20%28draft.getTag%28%27draft%27%29%29%20%7B%5Cn%20%20if%20%28draft.getTag%28%27draft%27%29.trim%28%29%20%3D%3D%20%27%27%29%20%7B%5Cn%20%20%20%20stopAction%28%29%5Cn%20%20%7D%5Cn%7D%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Script%22%0A%20%20%7D%0A%5D&description=Stop%20action%20if%20draft%20is%20empty%20or%20invalid.%20&modifiedAt=2017-05-04%2017%3A10%3A32%20%2B0000&name=Validate%20Draft&iconImageName=action_include

Share

x-drafts4://x-callback-url/import_action?v=2&tintColor=%5B%0A%20%200.451%2C%0A%20%200.29%2C%0A%20%200.553%0A%5D&shouldConfirm=0&logLevel=2&uuid=05078A78-A67F-428A-81B6-E51807C26BA0&disposition=2&actionSteps=%5B%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Metadata%20IN%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Validate%20Draft%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Metadata%20OUT%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22fileTemplate%22%20%3A%20%22%5B%5Bdraft_filtered%5D%5D%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Open%20in%22%2C%0A%20%20%20%20%22fileExtensionTemplate%22%20%3A%20%22%5B%5Bfile_ext%5D%5D%22%2C%0A%20%20%20%20%22fileNameTemplate%22%20%3A%20%22%5B%5Bfile_name%5D%5D%22%0A%20%20%7D%0A%5D&description=Open%20draft%20as%20a%20text%20file%20in%20another%20app%20which%20support%20%22Open%20in%22%20style%20import.&modifiedAt=2017-05-04%2017%3A55%3A28%20%2B0000&name=Share&iconImageName=action_share

Save

x-drafts4://x-callback-url/import_action?v=2&tintColor=%5B%0A%20%200.898%2C%0A%20%200.604%2C%0A%20%200.157%0A%5D&shouldConfirm=0&logLevel=2&uuid=AE3A2A2A-7089-449C-8950-BCD8D7450BB7&disposition=2&actionSteps=%5B%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Metadata%20IN%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Validate%20Draft%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Metadata%20OUT%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22fileTemplate%22%20%3A%20%22%5B%5Bdraft_filtered%5D%5D%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Export%22%2C%0A%20%20%20%20%22fileExtensionTemplate%22%20%3A%20%22%22%2C%0A%20%20%20%20%22fileNameTemplate%22%20%3A%20%22%5B%5Bfile_name%5D%5D.%5B%5Bfile_ext%5D%5D%22%0A%20%20%7D%0A%5D&description=Open%20draft%20as%20a%20text%20file%20in%20another%20app%20which%20support%20%22Open%20in%22%20style%20import.&modifiedAt=2017-05-04%2018%3A06%3A07%20%2B0000&name=Save&iconImageName=522-floppy-disk

Send Note to DEVONthink

x-drafts4://x-callback-url/import_action?v=2&tintColor=%5B%0A%20%200.769%2C%0A%20%200.141%2C%0A%20%200.161%0A%5D&shouldConfirm=0&logLevel=1&uuid=6D51EB54-AFBD-428F-B9DB-505539F253AB&disposition=2&actionSteps=%5B%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Metadata%20IN%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Validate%20Draft%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22scriptText%22%20%3A%20%22let%20%24meta%20%3D%20JSON.parse%28draft.getTag%28%27meta%27%29%29%5Cn%5Cnif%20%28%21%24meta%5B%27title%27%5D%29%20%7B%5Cn%20%20%24meta%5B%27title%27%5D%20%3D%20draft.getTag%28%27document_title%27%29%5Cn%7D%5Cn%5Cnif%20%28%21%24meta%5B%27css%27%5D%29%20%7B%5Cn%20%20%24meta%5B%27css%27%5D%20%3D%20%27http%3A%5C%2F%5C%2Fcss.example.org%27%5Cn%7D%5Cn%5Cndraft.defineTag%28%27meta%27%2C%20JSON.stringify%28%24meta%29%29%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Script%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Metadata%20OUT%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%2C%0A%20%20%7B%0A%20%20%20%20%22actionStepType%22%20%3A%20%22URL%22%2C%0A%20%20%20%20%22urlTemplate%22%20%3A%20%22x-devonthink%3A%5C%2F%5C%2FcreateMarkdown%3Ftitle%3D%5B%5Bdocument_title%5D%5D%26text%3D%5B%5Bdraft_filtered%5D%5D%26location%3D%5B%5Bdraft_open_url%5D%5D%22%2C%0A%20%20%20%20%22useSafariViewController%22%20%3A%20false%2C%0A%20%20%20%20%22encodeTags%22%20%3A%20true%0A%20%20%7D%0A%5D&description=&modifiedAt=2017-05-05%2018%3A00%3A50%20%2B0000&name=Send%20Note%20to%20DEVONthink&iconImageName=587-dictionary

Import with DEVONthink

x-drafts4://x-callback-url/import_action?v=2&tintColor=%5B%0A%20%200.031%2C%0A%20%200.455%2C%0A%20%200.627%0A%5D&shouldConfirm=0&logLevel=1&uuid=4D2805B9-4856-4195-97D4-3A2471912DBB&disposition=2&actionSteps=%5B%0A%20%20%7B%0A%20%20%20%20%22includedActionName%22%20%3A%20%22Share%22%2C%0A%20%20%20%20%22actionStepType%22%20%3A%20%22Include%20Action%22%0A%20%20%7D%0A%5D&description=&modifiedAt=2017-05-05%2017%3A59%3A57%20%2B0000&name=Import%20with%20DEVONthink&iconImageName=587-dictionary
#! python3
import re
from get_input import get_input
from open_url import open_url
def main():
# Get input (file, url, text or clipboard)
input, type, source = get_input()
# Remove "Snippet: " (text from Pushover)
input = re.sub(r"(^Snippet:\s)", "", input, 0).strip()
# Regex for meta block
meta_regex = r"^(-{3} *[\n\r])?([- \t\w]+:[ \t]*.+[\n\r])+(-{3}|\.{3})? *"
# Extract meta block as dictionary
meta = {}
result = re.match(meta_regex, input, re.I)
if result:
for match in result.group().split('\n'):
if ":" in match:
key = match.split(':', 1)[0].strip()
val = match.split(':', 1)[1].strip()
meta[key] = val;
if source is not None:
if type == 'file':
meta['file'] = source.split('/')[-1]
if type == 'url':
meta['url'] = source
# Remove meta block from input
text = re.sub(meta_regex, '', input, re.I).strip()
# Add two spaces to the end of each line
text = re.sub(r"([^\s])\n", r"\1 \n", text).strip()
# Reduce extra spaces to no more than two
text = re.sub(r"([ ]{3,}\n)", r" \n", text).strip()
# Start output with meta
output = ""
for key in ['file', 'url', 'title']:
if key in meta:
output = output + key + ': ' + (meta.pop(key)) + ' \n'
for key, val in meta.items():
output = output + key + ': ' + val + ' \n'
# Add text to output
if output:
output = output + '\n' + text
else:
output = text
# Open in Drafts using its URL
open_url("x-drafts4://x-callback-url/create", { 'text': output })
if __name__ == '__main__':
main()
#! python3
# This belongs in:
# ~/Modules & Templates/site-packages-3/get_input.py
import sys
import console
import appex
import clipboard
import requests
# Read text from file, sensitive to encoding
def read_file(path):
value = False
encodings = ['utf-8', 'ascii', 'iso-8859-1', 'windows-1252']
for enc in encodings:
try:
with open(path, 'r', encoding=enc) as fd:
value = fd.read()
break
except Exception:
if enc == encodings[-1]:
console.hud_alert('Unknown file encoding!')
sys.exit(1)
continue
return value
# Return input, type, source
def get_input():
# If running in share sheet, get text from
# input or from input file contents
if appex.is_running_extension():
# Read text from shared file as input
if appex.get_file_path():
return read_file(appex.get_file_path()), 'file', appex.get_file_path()
# Use shared text as input
elif appex.get_text():
return appex.get_text(), 'text', None
# Use shared URL as input
elif appex.get_url():
return requests.get(appex.get_url()).text, 'url', appex.get_url()
# Use input passed as an argument
argv = sys.argv
argv.pop(0)
if argv:
return " ".join(argv), 'argv', sys.argv
# Default to clipboard text as input
return clipboard.get(), 'clipboard', None
#! python3
# This belongs in:
# ~/Modules & Templates/site-packages-3/get_input.py
import appex
import urllib.parse
from objc_util import *
# Read text from file, sensitive to encoding
def open_url(url, param = {}, finish = True):
# Build URL
url = url + "?" + urllib.parse.urlencode(param)
# open application, even from extension
app = UIApplication.sharedApplication()
app.openURL_(nsurl(url))
# Close the share sheet
if finish:
appex.finish()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment