From 4b9f7408aea280ce66c3a0fdde79b02caa4eb500 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 11:33:02 +1200 Subject: [PATCH 01/14] extension -> firefox --- {extension => firefox}/background.js | 0 {extension => firefox}/hili.js | 0 {extension => firefox}/icons/icon-48.png | Bin {extension => firefox}/manifest.json | 0 {extension => firefox}/options.html | 0 {extension => firefox}/options.js | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {extension => firefox}/background.js (100%) rename {extension => firefox}/hili.js (100%) rename {extension => firefox}/icons/icon-48.png (100%) rename {extension => firefox}/manifest.json (100%) rename {extension => firefox}/options.html (100%) rename {extension => firefox}/options.js (100%) diff --git a/extension/background.js b/firefox/background.js similarity index 100% rename from extension/background.js rename to firefox/background.js diff --git a/extension/hili.js b/firefox/hili.js similarity index 100% rename from extension/hili.js rename to firefox/hili.js diff --git a/extension/icons/icon-48.png b/firefox/icons/icon-48.png similarity index 100% rename from extension/icons/icon-48.png rename to firefox/icons/icon-48.png diff --git a/extension/manifest.json b/firefox/manifest.json similarity index 100% rename from extension/manifest.json rename to firefox/manifest.json diff --git a/extension/options.html b/firefox/options.html similarity index 100% rename from extension/options.html rename to firefox/options.html diff --git a/extension/options.js b/firefox/options.js similarity index 100% rename from extension/options.js rename to firefox/options.js From 0d06e6874108b41f37264cf3c88089213082e0f4 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 11:33:08 +1200 Subject: [PATCH 02/14] add devonthink client --- devonthink/HiliClip.applescript | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 devonthink/HiliClip.applescript diff --git a/devonthink/HiliClip.applescript b/devonthink/HiliClip.applescript new file mode 100644 index 0000000..a5af4eb --- /dev/null +++ b/devonthink/HiliClip.applescript @@ -0,0 +1,49 @@ +(* + Clip contents of existing selection to a hili server. + See https://github.com/breezykermo/hili for more info. +*) +on replace_chars(this_text, search_string, replacement_string) + set AppleScript's text item delimiters to the search_string + set the item_list to every text item of this_text + set AppleScript's text item delimiters to the replacement_string + set this_text to the item_list as string + set AppleScript's text item delimiters to "" + return this_text +end replace_chars + +tell application id "DNtp" + try + set theDoc to the content record of think window 1 + set theRefURL to the reference URL of theDoc as string + set thePage to ((the current page of think window 1) as string) + set theUrl to theRefURL & "?page=" & (thePage as string) + + set theCitedText to the (selected text of think window 1 as string) + if theCitedText is "" then + (* Scenario 2: Document is open, but no text is highlighted. *) + set theQuotedText to missing value + else + (* Scenario 3: Document is open, text is highlighted. *) + set theQuotedText to my theCitedText + end if + + -- set theTags to the tags of theDoc + set _note to display dialog "note" default answer "" buttons {"Cancel", "Continue"} default button "Continue" + set _tags to display dialog "tags" default answer "" buttons {"Cancel", "Continue"} default button "Continue" + set theNote to the text returned of _note + set theTags to the text returned of _tags + set theQuote to my replace_chars(theQuotedText, "\\n", "\\\\n") + + do shell script "touch /tmp/args.txt" + do shell script "echo " & quoted form of theQuote & " > /tmp/args.txt" + do shell script "echo '*--ENDQUOTE--*' >> /tmp/args.txt" + do shell script "echo " & quoted form of theNote & " >> /tmp/args.txt" + do shell script "echo " & quoted form of theTags & " >> /tmp/args.txt" + do shell script "echo " & quoted form of theUrl & " >> /tmp/args.txt" + + do shell script "cd /Users/lachlankermode/code/pkb/hili/clients/python && python3 clipli.py > /tmp/hili_clip_log.txt" + + on error error_message number error_number + if the error_number is not -128 then display alert "DEVONthink Pro" message error_message as warning + end try +end tell From 46d1c53c68ad937ff96144f0c85a631f6f613c6b Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 11:33:13 +1200 Subject: [PATCH 03/14] add python client --- python/clip.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++ python/example.txt | 5 +++ 2 files changed, 93 insertions(+) create mode 100644 python/clip.py create mode 100644 python/example.txt diff --git a/python/clip.py b/python/clip.py new file mode 100644 index 0000000..0ce66a4 --- /dev/null +++ b/python/clip.py @@ -0,0 +1,88 @@ +import sys +import os +import json +import requests +import time + +# cheap args +env_args = os.environ.get("ARGS") +env_url = os.environ.get("URL") +env_pw = os.environ.get("PASSWORD") + +if env_url is None: + sys.exit("You must specify a server 'URL' in the environment.") + +# globals +ARGS = env_args if env_args is not None else "/tmp/args.txt" +SERVER_URL = env_url +PASSWORD = env_pw +CACHE = "./cached_clips.json" # not in temp so it isn't removed + + +def send(body): + headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + if PASSWORD is not None: + headers["Authentication"] = PASSWORD + + requests.post( + SERVER_URL, + headers = headers, + json = body + ) + + +def attempt_clip(clip): + try: + send(clip) + + # if clip is successful, flush all cached + if os.path.exists(CACHE): + with open(CACHE, "r") as c: + cached_clips = [json.loads(l) for l in c.readlines()] + + for cached_clip in cached_clips: + send(cached_clip) + + os.remove(CACHE) + + # TODO: only catch the specifics + except requests.ConnectionError: + is_first = not os.path.exists(CACHE) + with open(CACHE, "a") as cache: + if not is_first: cache.write("\n") + json.dump(clip, cache) + print("No internet connection, dumped to cache") + + +def run(): + with open(ARGS, 'r') as f: + data = f.readlines() + tm = int(round(time.time() * 1000)) + + idx = 0 + quote = "" + while data[idx] != "*--ENDQUOTE--*\n" and idx < len(data): + quote += data[idx] + idx += 1 + idx += 1 + + note = data[idx].rstrip("\n").strip() + tags = data[idx + 1].rstrip("\n").strip().split(",") + url = data[idx + 2].rstrip("\n").strip() + + clip = { + "time": tm, + "quote": quote, + "note": note, + "tags": tags, + "href": url + } + + attempt_clip(clip) + + +if __name__ == "__main__": + run() diff --git a/python/example.txt b/python/example.txt new file mode 100644 index 0000000..3233749 --- /dev/null +++ b/python/example.txt @@ -0,0 +1,5 @@ +This is the quote you want to send to hili, usually drawn from whatever is highlighted in an application at the time of clipping. +*--ENDQUOTE--* +Here is the note that you've written, normally entered through a prompt at clip time. +test,example +https://lachlankermode.com/example-url-for-hili From 5822654aa13a8f49f599c21709638e5f4204bb49 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 11:33:43 +1200 Subject: [PATCH 04/14] move into clients folder --- .../devonthink}/HiliClip.applescript | 0 {firefox => clients/firefox}/background.js | 0 {firefox => clients/firefox}/hili.js | 0 {firefox => clients/firefox}/icons/icon-48.png | Bin {firefox => clients/firefox}/manifest.json | 0 {firefox => clients/firefox}/options.html | 0 {firefox => clients/firefox}/options.js | 0 {python => clients/python}/clip.py | 0 {python => clients/python}/example.txt | 0 .../scriptable-ios}/README.md | 0 .../scriptable-ios}/action.js | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {devonthink => clients/devonthink}/HiliClip.applescript (100%) rename {firefox => clients/firefox}/background.js (100%) rename {firefox => clients/firefox}/hili.js (100%) rename {firefox => clients/firefox}/icons/icon-48.png (100%) rename {firefox => clients/firefox}/manifest.json (100%) rename {firefox => clients/firefox}/options.html (100%) rename {firefox => clients/firefox}/options.js (100%) rename {python => clients/python}/clip.py (100%) rename {python => clients/python}/example.txt (100%) rename {scriptable-ios => clients/scriptable-ios}/README.md (100%) rename {scriptable-ios => clients/scriptable-ios}/action.js (100%) diff --git a/devonthink/HiliClip.applescript b/clients/devonthink/HiliClip.applescript similarity index 100% rename from devonthink/HiliClip.applescript rename to clients/devonthink/HiliClip.applescript diff --git a/firefox/background.js b/clients/firefox/background.js similarity index 100% rename from firefox/background.js rename to clients/firefox/background.js diff --git a/firefox/hili.js b/clients/firefox/hili.js similarity index 100% rename from firefox/hili.js rename to clients/firefox/hili.js diff --git a/firefox/icons/icon-48.png b/clients/firefox/icons/icon-48.png similarity index 100% rename from firefox/icons/icon-48.png rename to clients/firefox/icons/icon-48.png diff --git a/firefox/manifest.json b/clients/firefox/manifest.json similarity index 100% rename from firefox/manifest.json rename to clients/firefox/manifest.json diff --git a/firefox/options.html b/clients/firefox/options.html similarity index 100% rename from firefox/options.html rename to clients/firefox/options.html diff --git a/firefox/options.js b/clients/firefox/options.js similarity index 100% rename from firefox/options.js rename to clients/firefox/options.js diff --git a/python/clip.py b/clients/python/clip.py similarity index 100% rename from python/clip.py rename to clients/python/clip.py diff --git a/python/example.txt b/clients/python/example.txt similarity index 100% rename from python/example.txt rename to clients/python/example.txt diff --git a/scriptable-ios/README.md b/clients/scriptable-ios/README.md similarity index 100% rename from scriptable-ios/README.md rename to clients/scriptable-ios/README.md diff --git a/scriptable-ios/action.js b/clients/scriptable-ios/action.js similarity index 100% rename from scriptable-ios/action.js rename to clients/scriptable-ios/action.js From cf8835cd41d17a6ea50abf89a04e2ab3a748e036 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 11:39:02 +1200 Subject: [PATCH 05/14] update readme --- readme.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 91b3b64..95acf62 100644 --- a/readme.md +++ b/readme.md @@ -1,18 +1,30 @@ # hili -Firefox extension to highlight and save text and images from the web. +Barebones server that listens for clips to save text and images from the web. +Includes a set of clients to send clips from various apps: -If you are offline, `hili` queues the snippets to send when a connection returns (you need to leave the tab open until they are successfully sent; there's a little indicator that lets you know how many snippets are queued). +- Firefox extension +- iOS (via [Scriptable](https://scriptable.app/)) +- [DevonTHINK](https://www.devontechnologies.com/) +- Python -## Extension +Firefox is the best supported client. If you are offline, `hili` queues the +snippets to send when a connection returns (you need to leave the tab open +until they are successfully sent; there's a little indicator that lets you know +how many snippets are queued). -To install (Firefox), open `about:debugging` and choose "Load Temporary Add-on", then select the `manifest.json` file. This is temporary (but useful for development); the add-on will be gone next time you run Firefox. +## Firefox + +To install (Firefox), open `about:debugging` and choose "Load Temporary +Add-on", then select the `manifest.json` file. This is temporary (but useful +for development); the add-on will be gone next time you run Firefox. To install it more permanently: If you're running Firefox Developer Edition, you should be able to: 1. Zip up the `extensions` directory -2. Go to `about:addons`, then `Install Add-on From File`, and select the zipped extension +2. Go to `about:addons`, then `Install Add-on From File`, and select the zipped + extension Otherwise, the process is more involved: 1. Go to `https://addons.mozilla.org/en-US/developers/addon/api/key/` (create a Firefox account if necessary) and generate credentials From 52cf039a36131ae5c2fded518c07ab1ced9fd669 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 11:42:21 +1200 Subject: [PATCH 06/14] add docker support --- Dockerfile | 5 +++++ start_docker | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Dockerfile create mode 100755 start_docker diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f597614 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.9-slim-buster +RUN mkdir -p /app +WORKDIR /app +ADD server.py /app/server.py +EXPOSE 8000 diff --git a/start_docker b/start_docker new file mode 100755 index 0000000..6f1499d --- /dev/null +++ b/start_docker @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +IMGNAME=hili +EXPOSED_PORT=4000 +CONTAINER_PORT=8000 +CLIPS_FILE=clips.xml +ASSETS_DIR=assets +KEY_FLAG="" + +# build docker image if it doesn't exist +# TODO: put on docker hub and cut this +if [[ "$(docker images -q $IMGNAME 2> /dev/null)" == "" ]]; then + echo "Building $IMGNAME image locally..." + docker build -t $IMGNAME . + echo "$IMGNAME image built." +fi + +# read $KEY from environment +if [[ ! -z "$KEY" ]]; then + KEY_FLAG="-k $KEY" +fi + +runsetup() { + if [[ "$#" -ne 2 ]]; then + echo "No arguments passed, logging to '$CLIPS_FILE' by default" + else + CLIPS_FILE=$1 + ASSETS_DIR=$2 + fi + if [ ! -f "$CLIPS_FILE" ]; then + touch $CLIPS_FILE + fi +} + +STARTUP_CMD="python server.py /app/$CLIPS_FILE /app/$ASSETS_DIR -p $CONTAINER_PORT $KEY_FLAG" + +# interpolate necessary args and start container +runsetup $@ && docker run -d \ + --name $IMGNAME \ + -p $EXPOSED_PORT:$CONTAINER_PORT \ + -v $(pwd)/$CLIPS_FILE:/app/$CLIPS_FILE \ + -v $(pwd)/$ASSETS_DIR:/app/$ASSETS_DIR \ + $IMGNAME $STARTUP_CMD + +echo "$IMGNAME running in docker, available on port $EXPOSED_PORT" From b18b906cb669723935debf8af28664f81c1e48cd Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 12:19:53 +1200 Subject: [PATCH 07/14] correct docker support --- Dockerfile | 1 - start_docker | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index f597614..6ca7860 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,4 +2,3 @@ FROM python:3.9-slim-buster RUN mkdir -p /app WORKDIR /app ADD server.py /app/server.py -EXPOSE 8000 diff --git a/start_docker b/start_docker index 6f1499d..25c6b84 100755 --- a/start_docker +++ b/start_docker @@ -1,8 +1,8 @@ #!/usr/bin/env bash IMGNAME=hili EXPOSED_PORT=4000 -CONTAINER_PORT=8000 -CLIPS_FILE=clips.xml +CONTAINER_PORT=8888 +CLIPS_FILE=clips.json ASSETS_DIR=assets KEY_FLAG="" From c5d1fc93ed0cc4fbf774466f32de3396ec455bd4 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 12:20:10 +1200 Subject: [PATCH 08/14] add non-breaking support for quote/note --- server.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index 5a6d03e..936b059 100644 --- a/server.py +++ b/server.py @@ -19,6 +19,9 @@ parser.add_argument('-k', '--key', type=str, dest='KEY', default=None, help='Secret key to authenticate clients') args = parser.parse_args() +def index_with_fallback(obj, fst, snd): + return obj.get(fst) if obj.get(fst) is not None else obj.get(snd) + class JSONRequestHandler(BaseHTTPRequestHandler): def do_POST(self): @@ -99,6 +102,11 @@ def do_GET(self): .highlight { margin: 2em 0; } + .note { + margin-top: 0.5em; + text-align: right; + font-size: 0.9em; + } .tags { color: #888; margin-top: 1em; @@ -121,7 +129,7 @@ def do_GET(self): for href, group in sorted(grouped.items(), key=lambda g: -max([d['time'] for d in g[1]])): html.append('''
-

{title}

'''.format(href=href, title=group[0]['title'])) +

{title}

'''.format(href=href, title=group[0].get('title'))) for d in group: if 'file' in d: # fname = d['file']['name'] @@ -134,17 +142,19 @@ def do_GET(self): '''.format( # src=os.path.join(args.UPLOAD_DIR, fname), src=d['file']['src'], - text=d['text'], + text=index_with_fallback(d, 'text', 'note'), tags=', '.join(d['tags']) )) else: html.append('''
{html} +
{note}
{tags}
'''.format( - html=d['html'], + html=index_with_fallback(d, 'html', 'quote'), + note=index_with_fallback(d, 'text', 'note'), tags=', '.join(d['tags']) )) html.append('
') From 98c655691017c1c4a55b1888768da536d15ff17d Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 12:20:23 +1200 Subject: [PATCH 09/14] update python to send title --- .gitignore | 2 ++ clients/python/clip.py | 12 ++++++++++-- clients/python/example.txt | 2 ++ clients/python/readme.md | 7 +++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 clients/python/readme.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88aea87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +clips.json +assets/ diff --git a/clients/python/clip.py b/clients/python/clip.py index 0ce66a4..15a35cd 100644 --- a/clients/python/clip.py +++ b/clients/python/clip.py @@ -63,22 +63,30 @@ def run(): tm = int(round(time.time() * 1000)) idx = 0 + title = "" + while data[idx] != "*--STARTQUOTE--*\n" and idx < len(data): + title += data[idx] + idx += 1 + + idx += 1 # skip STARTQUOTE quote = "" while data[idx] != "*--ENDQUOTE--*\n" and idx < len(data): quote += data[idx] idx += 1 - idx += 1 + idx += 1 # skip ENDQUOTE note = data[idx].rstrip("\n").strip() tags = data[idx + 1].rstrip("\n").strip().split(",") + if len(tags) == 1 and tags[0] == "": tags = [] url = data[idx + 2].rstrip("\n").strip() clip = { "time": tm, + "title": title, "quote": quote, "note": note, "tags": tags, - "href": url + "href": url, } attempt_clip(clip) diff --git a/clients/python/example.txt b/clients/python/example.txt index 3233749..f8f4fee 100644 --- a/clients/python/example.txt +++ b/clients/python/example.txt @@ -1,3 +1,5 @@ +Title of the document +*--STARTQUOTE--* This is the quote you want to send to hili, usually drawn from whatever is highlighted in an application at the time of clipping. *--ENDQUOTE--* Here is the note that you've written, normally entered through a prompt at clip time. diff --git a/clients/python/readme.md b/clients/python/readme.md new file mode 100644 index 0000000..a1139fe --- /dev/null +++ b/clients/python/readme.md @@ -0,0 +1,7 @@ +Simple python script to send a clip to a hili server. + +## Run +```bash +URL=http://localhost:8888 ARGS=$(pwd)/example.txt python clip.py +``` + From c123437fe52f814aca7a3b754a14326e45e877ea Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 12:32:37 +1200 Subject: [PATCH 10/14] document docker orchestration in docker-compose rather than bash script --- docker-compose.yml | 12 ++++++++++++ start_docker | 44 -------------------------------------------- 2 files changed, 12 insertions(+), 44 deletions(-) create mode 100644 docker-compose.yml delete mode 100755 start_docker diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1063f92 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.9" +services: + hili: + build: . + command: python server.py /app/clips.json /app/assets + volumes: + - ./clips.json:/app/clips.json + - ./assets:/app/assets + ports: + - "8888:8888" + + diff --git a/start_docker b/start_docker deleted file mode 100755 index 25c6b84..0000000 --- a/start_docker +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -IMGNAME=hili -EXPOSED_PORT=4000 -CONTAINER_PORT=8888 -CLIPS_FILE=clips.json -ASSETS_DIR=assets -KEY_FLAG="" - -# build docker image if it doesn't exist -# TODO: put on docker hub and cut this -if [[ "$(docker images -q $IMGNAME 2> /dev/null)" == "" ]]; then - echo "Building $IMGNAME image locally..." - docker build -t $IMGNAME . - echo "$IMGNAME image built." -fi - -# read $KEY from environment -if [[ ! -z "$KEY" ]]; then - KEY_FLAG="-k $KEY" -fi - -runsetup() { - if [[ "$#" -ne 2 ]]; then - echo "No arguments passed, logging to '$CLIPS_FILE' by default" - else - CLIPS_FILE=$1 - ASSETS_DIR=$2 - fi - if [ ! -f "$CLIPS_FILE" ]; then - touch $CLIPS_FILE - fi -} - -STARTUP_CMD="python server.py /app/$CLIPS_FILE /app/$ASSETS_DIR -p $CONTAINER_PORT $KEY_FLAG" - -# interpolate necessary args and start container -runsetup $@ && docker run -d \ - --name $IMGNAME \ - -p $EXPOSED_PORT:$CONTAINER_PORT \ - -v $(pwd)/$CLIPS_FILE:/app/$CLIPS_FILE \ - -v $(pwd)/$ASSETS_DIR:/app/$ASSETS_DIR \ - $IMGNAME $STARTUP_CMD - -echo "$IMGNAME running in docker, available on port $EXPOSED_PORT" From aea3ec5be1421d902b1410754adb7ea0f1b2ad9e Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 12:36:21 +1200 Subject: [PATCH 11/14] add note entry to firefox extension --- clients/firefox/hili.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clients/firefox/hili.js b/clients/firefox/hili.js index 0deb0a8..64199a4 100644 --- a/clients/firefox/hili.js +++ b/clients/firefox/hili.js @@ -128,6 +128,7 @@ function highlightText() { html = getSelectionHtml(selection); } if (text) { + let note = prompt('Note', '').trim() let tags = prompt('Tags', last_tags).split(',').map((t) => t.trim()); if (tags == null) return; last_tags = tags.join(', '); @@ -135,9 +136,10 @@ function highlightText() { href: cleanUrl(window.location.href), title: document.title, time: +new Date(), - text: text, - html: html, - tags: tags + text, + html, + note, + tags, }; post(data); } @@ -182,7 +184,7 @@ const marketingRegex = /(utm_.+|mc_.+|cmpid|truid|CMP)/; function cleanUrl(url) { url = new URL(url); let toDelete = new Set(); - for (let entry of url.searchParams) { + for (let entry of url.searchParams) { let [key, val] = entry; if (marketingRegex.test(key)) { toDelete.add(key); From 46a9b61f63e59993d59b0aed2ea57ac0ebcc7ef9 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 12:37:42 +1200 Subject: [PATCH 12/14] standardise 'quote' as 'text' in python --- clients/python/clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/python/clip.py b/clients/python/clip.py index 15a35cd..2ad996f 100644 --- a/clients/python/clip.py +++ b/clients/python/clip.py @@ -83,7 +83,7 @@ def run(): clip = { "time": tm, "title": title, - "quote": quote, + "text": quote, "note": note, "tags": tags, "href": url, From d2d78466660d6ff0e6eac18543266b170e4ee7e6 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 5 Apr 2021 12:46:23 +1200 Subject: [PATCH 13/14] genericise devonthink script --- clients/devonthink/HiliClip.applescript | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clients/devonthink/HiliClip.applescript b/clients/devonthink/HiliClip.applescript index a5af4eb..bd71e77 100644 --- a/clients/devonthink/HiliClip.applescript +++ b/clients/devonthink/HiliClip.applescript @@ -14,6 +14,7 @@ end replace_chars tell application id "DNtp" try set theDoc to the content record of think window 1 + set theTitle to ((the name without extension of theDoc) as string) set theRefURL to the reference URL of theDoc as string set thePage to ((the current page of think window 1) as string) set theUrl to theRefURL & "?page=" & (thePage as string) @@ -35,13 +36,16 @@ tell application id "DNtp" set theQuote to my replace_chars(theQuotedText, "\\n", "\\\\n") do shell script "touch /tmp/args.txt" - do shell script "echo " & quoted form of theQuote & " > /tmp/args.txt" + do shell script "echo " & quoted form of theTitle & " > /tmp/args.txt" + do shell script "echo '*--STARTQUOTE--*' >> /tmp/args.txt" + do shell script "echo " & quoted form of theQuote & " >> /tmp/args.txt" do shell script "echo '*--ENDQUOTE--*' >> /tmp/args.txt" do shell script "echo " & quoted form of theNote & " >> /tmp/args.txt" do shell script "echo " & quoted form of theTags & " >> /tmp/args.txt" do shell script "echo " & quoted form of theUrl & " >> /tmp/args.txt" - do shell script "cd /Users/lachlankermode/code/pkb/hili/clients/python && python3 clipli.py > /tmp/hili_clip_log.txt" + (* NOTE: change the following line to suit your system *) + do shell script "cd /absolute/path/to/hili/clients/python && URL='http://localhost:8888' python3 clip.py > /tmp/hili_clip_log.txt" on error error_message number error_number if the error_number is not -128 then display alert "DEVONthink Pro" message error_message as warning From f86d7e8413600efa0133830e2896c499c26b674a Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 6 Apr 2021 22:12:06 +1200 Subject: [PATCH 14/14] pass overrides as CLI arg --- clients/python/clip.py | 2 +- server.py | 33 +++++++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/clients/python/clip.py b/clients/python/clip.py index 2ad996f..24a135d 100644 --- a/clients/python/clip.py +++ b/clients/python/clip.py @@ -83,7 +83,7 @@ def run(): clip = { "time": tm, "title": title, - "text": quote, + "html": quote, "note": note, "tags": tags, "href": url, diff --git a/server.py b/server.py index 936b059..6fb18f4 100644 --- a/server.py +++ b/server.py @@ -17,10 +17,21 @@ parser.add_argument('UPLOAD_DIR', type=str, help='Directory to save uploaded files') parser.add_argument('-p', '--port', type=int, dest='PORT', default=8888, help='Port for server') parser.add_argument('-k', '--key', type=str, dest='KEY', default=None, help='Secret key to authenticate clients') +parser.add_argument('-o', '--key-overrides', type=str, dest='OVERRIDES', default=None, help='Key overrides for clips sent to server in the format "original1:new1;original2:new2"') args = parser.parse_args() -def index_with_fallback(obj, fst, snd): - return obj.get(fst) if obj.get(fst) is not None else obj.get(snd) +def idx(obj, base, overrides): + key = base + for override in overrides: + a,b = override.split(":") + if a == base: + key = b + return obj.get(key) if obj.get(key) is not None else obj.get(base) + +overrides = [] +if args.OVERRIDES: + overrides = args.OVERRIDES.split(";") + overrides = [x for x in overrides if len(x.split(":")) == 2] class JSONRequestHandler(BaseHTTPRequestHandler): @@ -124,15 +135,14 @@ def do_GET(self): grouped = defaultdict(list) for d in data: - grouped[d['href']].append(d) + grouped[idx(d, 'href', overrides)].append(d) - for href, group in sorted(grouped.items(), key=lambda g: -max([d['time'] for d in g[1]])): + for href, group in sorted(grouped.items(), key=lambda g: -max([idx(d, 'time', overrides) for d in g[1]])): html.append('''

{title}

'''.format(href=href, title=group[0].get('title'))) for d in group: if 'file' in d: - # fname = d['file']['name'] html.append('''
@@ -140,10 +150,9 @@ def do_GET(self):
{tags}
'''.format( - # src=os.path.join(args.UPLOAD_DIR, fname), - src=d['file']['src'], - text=index_with_fallback(d, 'text', 'note'), - tags=', '.join(d['tags']) + src=idx(d, 'file', overrides)['src'], + text=idx(d, 'text', overrides), + tags=', '.join(idx(d, 'tags', overrides)) )) else: html.append(''' @@ -153,9 +162,9 @@ def do_GET(self):
{tags}
'''.format( - html=index_with_fallback(d, 'html', 'quote'), - note=index_with_fallback(d, 'text', 'note'), - tags=', '.join(d['tags']) + html=idx(d, 'html', overrides), + note=idx(d, 'note', overrides), + tags=', '.join(idx(d, 'tags', overrides)) )) html.append('
')