Skip to content

[bin] Create tool to robustly open ChatGPT website and inject prompts #43

@0xdevalias

Description

@0xdevalias

Introduce a new helper script (currently named alfred-chatgpt-opener, though subject to change) designed to open the ChatGPT website, locate the prompt box, and insert content into it.

Directly embedding prompts into the URL works for smaller inputs, but fails with large ones due to HTTP 431 limits. This tool attempts to provide a more reliable solution by opening a browser window and injecting the prompt into the page DOM via AppleScript + JavaScript.

Intended use:

  • Primary: integrated into Alfred workflow to support the "Ask ChatGPT (via browser)" flow.
  • Secondary: can be invoked outside Alfred for general usage.

Current status:

  • Initial hacky WIP / PoC.
  • Likely not fully functional yet.
  • Includes Chrome + Safari AppleScript flows.
  • Supports optional AUTO_SEND flag to immediately submit after injection (fragile).

Next steps:

  • Test and confirm injection across browsers.
  • Improve DOM targeting reliability as ChatGPT frontend evolves.
  • Refine script interface (flags/env vars).
  • Decide on final naming (alfred-chatgpt-opener vs alternatives).

This is the current hacky WIP script:

#!/usr/bin/env zsh
# alfred-chatgpt-paster-targeted.zsh
# Reads prompt from stdin. Creates a new browser window with a unique _alfred_uuid in the URL,
# then injects the full prompt into the ProseMirror editor (id="prompt-textarea") and optionally clicks send.
#
# Env:
#   LIMIT (default 8000) - char threshold for URL vs paste
#   AUTO_SEND (0 or 1) - if 1, click Send after pasting (fragile)
#
SCRIPT_NAME="${0##*/}"
#LIMIT=${LIMIT:-8000}
LIMIT=${LIMIT:-1}
AUTO_SEND=${AUTO_SEND:-0}

# read prompt from stdin
prompt="$(cat -)"
prompt_trimmed="${prompt%$'\n'}"
len=${#prompt_trimmed}
uuid=$(uuidgen)
base_url="https://chat.openai.com/?_alfred_uuid=${uuid}"

# url-encode helper
urlencode() {
  python3 - <<PY - "$1"
import sys, urllib.parse
print(urllib.parse.quote(sys.argv[1], safe=''))
PY
}

if (( len <= LIMIT )); then
  encoded_q=$(urlencode "$prompt_trimmed")
  open "${base_url}&q=${encoded_q}"
  exit 0
fi

b64=$(printf "%s" "$prompt" | base64)

# JavaScript we will inject into the opened tab. It:
#  - verifies _alfred_uuid is present
#  - decodes base64 payload
#  - finds editor by id 'prompt-textarea' or .ProseMirror or the hidden textarea
#  - sets content and dispatches input events
#  - optionally clicks composer-submit-button to send
js_inject=$(cat <<'JSTEMPLATE'
(function() {
  try {
    var expectedUuid = '__ALFRED_UUID__';
    if (window.location.search.indexOf('_alfred_uuid=' + expectedUuid) === -1) {
      console.warn('Alfred: uuid mismatch; aborting paste.');
      false;
    } else {
      var b64 = '__PAYLOAD_B64__';
      var raw = '';
      try { raw = atob(b64); } catch(e) { console.error('Alfred: base64 decode failed', e); return false; }

      // Prefer the ProseMirror contenteditable with known id
      var box = document.getElementById('prompt-textarea') || document.querySelector('.ProseMirror') || document.querySelector('textarea[name="prompt-textarea"]');

      if (!box) {
        console.warn('Alfred: no prompt editor found');
        return false;
      }

      // If it's a hidden textarea (fallback), set value
      var isTextArea = (box.tagName && box.tagName.toLowerCase() === 'textarea') || (box.tagName && box.tagName.toLowerCase() === 'input');
      var isContentEditable = box.getAttribute && (box.getAttribute('contenteditable') === 'true' || box.classList.contains('ProseMirror'));

      // Insert text
      try {
        if (isContentEditable) {
          // Focus and replace text content so ProseMirror picks it up
          box.focus();
          // Prefer to replace with plain text
          if ('innerText' in box) box.innerText = raw;
          else box.textContent = raw;

          // Dispatch input event — ProseMirror usually listens for this
          var ev = new InputEvent('input', { bubbles: true, cancelable: true });
          box.dispatchEvent(ev);
        } else if (isTextArea) {
          box.focus();
          box.value = raw;
          var ev2 = new Event('input', { bubbles: true });
          box.dispatchEvent(ev2);
        } else {
          // As a last resort, attempt to paste via clipboard API
          try {
            navigator.clipboard.writeText(raw).then(function(){
              // try paste via execCommand (may require focus)
              box.focus();
              document.execCommand('paste');
            });
          } catch (e) {
            console.warn('Alfred: fallback clipboard paste failed', e);
            return false;
          }
        }
      } catch (err) {
        console.error('Alfred: insertion error', err);
        return false;
      }

      // Optionally click the send button if requested
      var autoSend = __AUTO_SEND_FLAG__;
      if (autoSend) {
        // try id first, then more general selectors
        var sendBtn = document.getElementById('composer-submit-button') || document.querySelector('button[data-testid="send-button"]') || document.querySelector('button[aria-label="Send prompt"]');
        if (sendBtn) {
          try {
            sendBtn.click();
          } catch(e) {
            // synthetic click failed
            console.warn('Alfred: send click attempted but failed', e);
          }
        } else {
          console.warn('Alfred: send button not found for auto-send');
        }
      }

      return true;
    }
  } catch (e) {
    console.error('Alfred: unexpected', e);
    return false;
  }
})();
JSTEMPLATE
)

# inject uuid, payload, auto_send flag into js_inject safely
js_inject="${js_inject//__ALFRED_UUID__/$uuid}"
js_inject="${js_inject//__PAYLOAD_B64__/$b64}"
js_inject="${js_inject//__AUTO_SEND_FLAG__/$AUTO_SEND}"

# AppleScript for Google Chrome that creates a NEW WINDOW (so we know the tab) and executes the JS
osascript_chrome=$(cat <<APPLESCRIPT
tell application "Google Chrome"
  if (count of windows) = 0 then make new window
  set newWindow to make new window with properties {URL:"$base_url"}
  set theTab to active tab of newWindow
  activate
  delay 0.35
  tell theTab to execute javascript "$js_inject"
end tell
APPLESCRIPT
)

# Attempt Chrome first
if osascript -e 'application "Google Chrome" is running' >/dev/null 2>&1; then
  osascript <<APPLESCRIPT
$osascript_chrome
APPLESCRIPT

  # Optimistically notify + copy to clipboard in case of race/failure
  /usr/bin/osascript -e 'display notification "Pasted prompt to new ChatGPT window. If it failed, it is copied to clipboard." with title "Alfred → ChatGPT"'
  printf "%s" "$prompt" | pbcopy
  exit 0
fi

# Safari fallback (similar injection)
osascript_safari=$(cat <<APPLESCRIPT
tell application "Safari"
  if (count of windows) = 0 then make new document
  set newWindow to make new document with properties {URL:"$base_url"}
  activate
  delay 0.35
  tell front document to do JavaScript "$js_inject"
end tell
APPLESCRIPT
)

osascript <<APPLESCRIPT
$osascript_safari
APPLESCRIPT

/usr/bin/osascript -e 'display notification "Pasted prompt to new ChatGPT window (Safari). If it failed, it is copied to clipboard." with title "Alfred → ChatGPT"'
printf "%s" "$prompt" | pbcopy
exit 0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions