Babel
AUTOMATION

AppleScript & Automation:
Build Your Own Integrations

Advanced, powerful, and critical for the people who build tools for everyone else

ADVANCED

APPLESCRIPT 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.

  • command addNote v : Add a note with the given text
  • parameter text : The note text as HTML or Markdown
  • result Note added to your inbox
  • use with Shortcuts, Python, shell scripts, launchctl
  • power level Advanced users only (and that's fine)
  • philosophy Few create macros, many benefit from them
  • 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

  • what Gary built Python script that fetches bookmarked tweets via Twitter API
  • runs via launchctl every hour
  • script does Pulls new bookmarks, formats as Markdown, calls addNote for each
  • result Bookmarked tweets appear in Pachinko inbox automatically
  • benefit Never manually copy tweets again
  • Example: Feedbin to Pachinko (Step-by-Step)

  • use case Star articles in Feedbin, auto-import to Pachinko
  • what it does Polls Feedbin API every 5 minutes for starred items, converts to Markdown, sends to Pachinko
  • requirements Python 3, markdownify library, Feedbin account
  • workflow Star in Feedbin, process in Pachinko-zero manual steps
  • 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

  • script won't run Check credentials, ensure Python 3 and markdownify installed
  • launchctl errors Check /tmp/feedbin-pachinko.err for error messages
  • nothing imports Verify Pachinko is running, check you have starred items in Feedbin
  • stop the script launchctl stop com.feedbin.pachinko && launchctl unload ~/Library/LaunchAgents/com.feedbin.pachinko.plist
  • 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:

  • Shortcuts Build iOS/Mac Shortcuts that call this AppleScript
  • Python/Shell Call osascript from your scripts to trigger addNote
  • launchctl Schedule scripts to run automatically (Gary's approach)
  • APIs Pull from any API (Twitter, RSS, webhooks), push to Pachinko
  • your imagination If you can script it, you can automate it
  • 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.

  • for technical users Comfortable with scripts, APIs, automation
  • creates value One person builds, whole team benefits
  • examples Auto-import bookmarks, starred items, webhooks, RSS feeds
  • support Simple command, powerful possibilities
  • our bet Supporting macro builders creates disproportionate value