AppleScript & Automation:
Build Your Own Integrations
Advanced, powerful, and critical for the people who build tools for everyone else
ADVANCEDAPPLESCRIPT SUPPORT
One Command: addNote
I worked on the team that shipped VBA 1.0 with Excel 5.0. We gave developers one simple thing: programmatic access to Excel. What happened next was incredible. People built trading systems, scientific models, business dashboards, and solutions we never imagined. The best part wasn't what we built - it was watching developers create things that mattered to them and their teams.
Pachinko's AppleScript support is the same idea. One command-addNote-that's it. Send it text (HTML or Markdown), and it adds a note to your inbox. Simple, but it unlocks everything. Shortcuts automation, Python scripts via launchctl, custom integrations with any service you use. We're not trying to predict what you'll build. We're giving you the tools and hoping you surprise us.
This is advanced, and that's okay. Most people won't use it, but the ones who do will build automations that make Pachinko work exactly how they need it. Your team lead builds a Shortcut that everyone uses. The developer writes a script that pulls research into the company inbox. Few create macros, but they create value for everyone else. We support these people well because they deserve it.
REAL EXAMPLES
What People Actually Build
This is advanced automation. If you're not technical, skip this episode or send it to someone on your team who is. If you understand cron jobs and shell scripts, you'll immediately see the potential.
Example: Auto-Import Twitter Bookmarks
Example: Feedbin to Pachinko (Step-by-Step)
GARY'S FEEDBIN SCRIPT
Complete Working Example
Here's Gary's actual Feedbin automation script. It polls the Feedbin API for starred articles, converts them to Markdown, and sends them to Pachinko. This is production code-copy it, modify it, use it.
Step 1: Install Dependencies
pip3 install markdownify
Step 2: Save the Script
Save this as feedbin_to_pachinko.py in your home directory or wherever you keep scripts:
#!/usr/bin/env python3
import base64
import json
import os
import sys
import time
import html
import re
import urllib.parse
import urllib.request
import subprocess
from pathlib import Path
from typing import Dict, List, Any, Optional
from markdownify import markdownify as md
FEEDBIN_API_BASE = "https://api.feedbin.com/v2"
STATE_PATH = Path.home() / ".feedbin_starred_state.json"
POLL_SECONDS = 300 # 5 minutes
USERNAME = "FIXME" # Replace with your Feedbin email
PASSWORD = "FIXME" # Replace with your Feedbin password
if not USERNAME or not PASSWORD or "FIXME" in [USERNAME, PASSWORD]:
sys.stderr.write("ERROR: Set USERNAME and PASSWORD in the script.\n")
sys.exit(1)
def _auth_header(username: str, password: str) -> Dict[str, str]:
raw = f"{username}:{password}".encode("utf-8")
b64 = base64.b64encode(raw).decode("ascii")
return {"Authorization": f"Basic {b64}", "User-Agent": "feedbin-starred-to-p/1.0"}
def http_get_json(url: str, headers: Dict[str, str]) -> Any:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=30) as resp:
data = resp.read()
return json.loads(data.decode("utf-8"))
def fetch_starred_ids() -> List[int]:
url = f"{FEEDBIN_API_BASE}/starred_entries.json"
headers = _auth_header(USERNAME, PASSWORD)
data = http_get_json(url, headers)
return [int(x) for x in data]
def chunked(seq, n):
for i in range(0, len(seq), n):
yield seq[i:i+n]
def fetch_entries_by_ids(ids: List[int]) -> Dict[int, Dict[str, Any]]:
headers = _auth_header(USERNAME, PASSWORD)
out: Dict[int, Dict[str, Any]] = {}
for group in chunked(ids, 100):
joined = ",".join(str(i) for i in group)
url = f"{FEEDBIN_API_BASE}/entries.json?ids={joined}"
entries = http_get_json(url, headers)
for e in entries:
out[int(e["id"])] = e
return out
IMG_SRC_RE = re.compile(r'<img[^>]+src=["\']([^"\']+)["\']', re.IGNORECASE)
TAG_RE = re.compile(r"<[^>]+")
def first_image_from_html(html_text: str) -> Optional[str]:
m = IMG_SRC_RE.search(html_text or "")
return m.group(1) if m else None
def strip_html_to_text(html_text: str) -> str:
text = TAG_RE.sub("", html_text or "")
text = html.unescape(text)
text = re.sub(r"\s+\n", "\n", text)
text = re.sub(r"[ \t]+", " ", text)
return text.strip()
def to_markdown(entry: Dict[str, Any]) -> str:
title = (entry.get("title") or "").strip()
url = (entry.get("url") or "").strip()
content_html = entry.get("content") or ""
markdown_body = md(content_html, heading_style="ATX", strip=["style", "script"]).strip()
summary = (entry.get("summary") or "").strip()
if not summary:
summary = strip_html_to_text(content_html)[:1000]
md_lines = []
if title and url:
md_lines.append(f"[{_md_escape_text(title)}]({url})")
elif title:
md_lines.append(_md_escape_text(title))
elif url:
md_lines.append(f"<{url}>")
if summary:
md_lines.append("")
md_lines.append(summary)
if markdown_body:
md_lines.append("")
md_lines.append(markdown_body)
if url:
md_lines.append("")
md_lines.append(f"{url}")
return "\n".join(md_lines).strip()
def _md_escape_text(s: str) -> str:
return s.replace("[", r"\[").replace("]", r"\]").replace("*", r"\*").replace("_", r"\_")
def load_state() -> Dict[str, Any]:
if STATE_PATH.exists():
try:
return json.loads(STATE_PATH.read_text("utf-8"))
except Exception:
pass
return {"seen_ids": []}
def save_state(state: Dict[str, Any]) -> None:
STATE_PATH.write_text(json.dumps(state, indent=2), encoding="utf-8")
def applescript_string_literal(s: str) -> str:
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
def send_to_pachinko(markdown: str) -> None:
script = f'tell application "Pachinko" to addNote {applescript_string_literal(markdown)}'
res = subprocess.run(
["/usr/bin/osascript", "-e", script],
text=True,
capture_output=True
)
if res.returncode != 0:
raise RuntimeError(f"osascript failed: {res.stderr.strip()}")
def process_new_ids(new_ids: List[int]) -> None:
if not new_ids:
return
entries_map = fetch_entries_by_ids(new_ids)
for eid in new_ids:
entry = entries_map.get(eid)
if not entry:
continue
md = to_markdown(entry)
try:
send_to_pachinko(md)
print(f"[OK] Sent entry #{eid}: {entry.get('title')!r}")
except Exception as e:
print(f"[ERR] Entry #{eid}: {e}", file=sys.stderr)
def main():
print("Feedbin starred watcher starting… (polling every 5 minutes)")
state = load_state()
seen_ids = set(int(x) for x in state.get("seen_ids", []))
LIMIT_ON_FIRST_RUN = 20
while True:
try:
starred_ids = fetch_starred_ids()
starred_ids_sorted = sorted(set(starred_ids))
new_ids = [i for i in starred_ids_sorted if i not in seen_ids][::-1]
if not seen_ids and LIMIT_ON_FIRST_RUN and len(new_ids) > LIMIT_ON_FIRST_RUN:
new_ids = new_ids[-LIMIT_ON_FIRST_RUN:]
if new_ids:
process_new_ids(new_ids)
seen_ids.update(new_ids)
save_state({"seen_ids": sorted(seen_ids)})
print(f"Heartbeat: starred={len(starred_ids_sorted)} seen={len(seen_ids)} new={len(new_ids)}")
except Exception as e:
print(f"[LOOP ERROR] {e}", file=sys.stderr)
time.sleep(POLL_SECONDS)
if __name__ == "__main__":
main()
Step 3: Configure Your Credentials
Edit the script and replace the FIXME values:
USERNAME = "your-feedbin-email@example.com"
PASSWORD = "your-feedbin-password"
Step 4: Make It Executable
chmod +x feedbin_to_pachinko.py
Step 5: Test It
Run the script manually to verify it works:
python3 feedbin_to_pachinko.py
You should see "Feedbin starred watcher starting…" and any starred articles will import to Pachinko. Press Ctrl+C to stop it.
Step 6: Run It Automatically with launchctl
Create a launchd plist file at ~/Library/LaunchAgents/com.feedbin.pachinko.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.feedbin.pachinko</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>/Users/YOURUSERNAME/feedbin_to_pachinko.py</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/feedbin-pachinko.log</string>
<key>StandardErrorPath</key>
<string>/tmp/feedbin-pachinko.err</string>
</dict>
</plist>
Replace YOURUSERNAME with your actual username. Find your Python path with:
which python3
Step 7: Load and Start
launchctl load ~/Library/LaunchAgents/com.feedbin.pachinko.plist
launchctl start com.feedbin.pachinko
Step 8: Verify It's Running
tail -f /tmp/feedbin-pachinko.log
You should see heartbeat messages every 5 minutes. Star an article in Feedbin and watch it appear in Pachinko automatically.
Troubleshooting
GETTING STARTED
Basic AppleScript Example
Here's the simplest possible AppleScript to add a note. Use this as your starting point, then build from there.
tell application "Pachinko"
addNote "This is my note text in **Markdown**"
end tell
That's it. Run this from Script Editor, and you'll see a note appear in your inbox. From here, you can:
WHO THIS IS FOR
Macro Builders, Not Macro Users
This tutorial is for the technical people who build tools that everyone else uses. The team member who writes the Shortcut that imports meeting notes. The developer who scripts their research workflow. The power user who automates everything. You understand launchctl, Python, and API calls. You see addNote and immediately think of ten integrations to build.
If this tutorial makes no sense to you, that's okay-it's not meant for you. But if you know someone technical who could build something useful, send them this. Macros are critical. Few create them, many benefit.