0

I am trying to write an AppleScript service to add a selected hyperlink to Safari reading list. For bare URLs this is easy. For instance, below is my service that receives selected URLs (input is "only URLs"):

on run {theUrl}

    using terms from application "Safari"
        tell application "Safari"
            add reading list item theUrl as string
        end tell
    end using terms from

    return theUrl
end run

However, this apparently does not work if the selected text is hyperlinked (but not bare URL), e.g., StackOverflow. Is there a way to make the service also work for hyperlinked texts? Thanks.

4ae1e1
  • 7,228
  • 8
  • 44
  • 77

2 Answers2

2

Sadly, there is no good solution, but you could try the following approach (rough outline):

  • Make your service accept text as input - this will enable it even when a hyperlink is selected (though it will effectively enable for any text)
  • Instead of processing the text passed in, send a copy-to-clipboard command to the active application (either by sending a keystroke or preferably through GUI scripting). (Note that even a service accepting rich text only passes plain text to the service handler).
  • Obtain the data as source text from the clipboard using the trick below - sadly, AS won't let you handle RTF or HTML natively.
  • Extract the URL from the source text and add it to the reading list.

To get RTF or HTML data from the clipboard some hacking is required (thanks, https://stackoverflow.com/a/2548429/45375):

set html to do shell script "osascript -e '«class HTML» of (the clipboard as record)' | perl -ne 'print chr foreach unpack(\"C*\",pack(\"H*\",substr($_,11,-3)))'"
set rtf to do shell script "osascript -e '«class RTF » of (the clipboard as record)' | perl -ne 'print chr foreach unpack(\"C*\",pack(\"H*\",substr($_,11,-3)))'"

You can parse the result with a regex to extract the URL.

Note that some apps put only RTF on the Clipboard (Safari), others put both HTML and RTF (Chrome).

If you're willing to tackle this, let me know if you need help with specific steps. Good luck.

Update: As requested, code for invoking the frontmost application's copy-to-clipboard command:

  • Simple, but not the most robust: Send ⌘-C (the issue is that if another key happens to be held down by the user while the keystrokes are sent, it can interfere):
tell application "System Events" to keystroke "c" using command down
  • Robust alternative: Use GUI scripting to invoke a menu command

The only caveat is that access for assistive devices must be enabled via System Preferences, up to 10.8, where this was a single global switch, this could be initiated from a script (with an admin password required to allow it; from 10.9, unfortunately, each individual application using GUI scripting must be authorized, which requires quite a bit of manual intervention from the end user - it's a one-time pain, though).

my copyToClipboard()

(* 
 # Copies the frontmost application's current selection to the clipboard
 # using GUI scripting rather than keyboard shortcuts so as to avoid conflicts 
 # (with keys held down by the user while the script is sending keystrokes).

 # CAVEAT: While this subroutine IS portable across *languages*, it does make an 
 # assumption that will hopefully hold for all applications: that the "Edit" menu is
 # the *4th* menu from the left (Apple menu, app menu, File, Edit).

*)
on copyToClipboard()
     tell application "System Events"
          tell menu 1 of menu bar item 4 of menu bar 1 of (first process whose frontmost is true)
               -- Find the menu item whose keyboard shortcut is Cmd-C
               set miCopy to first menu item whose value of attribute "AXMenuItemCmdChar" is "C" and value of attribute "AXMenuItemCmdModifiers" is 0
               click miCopy
          end tell
     end tell
end copyToClipboard

Finally, here's a subroutine for ensuring that access for assistive device is enabled; works both on 10.9 and before:

  • On 10.8 and earlier, it just triggers an admin-authorization prompt and, if authorized, continues to run.
  • On 10.9, the best it can do is to pop up a dialog with instructions and open the relevant System preferences pane - the rest is up to the user. Even if the user manages to authorize the app, though, it must be restarted for things to start working.
    # Example use.
    try
        my ensureAssistiveAccess()
    on error
       # Exit quietly, relying on the prompt to have provided
       # sufficient information.
       return
    end try


    # Tries to ensure that access for assistive devices is turned on so as to enable GUI scripting.
    # - Up to 10.8.x, access can be turned on programmatically, on demand - via an admin authorization prompt.
    # - From 10.9, the best we can do is display a GUI prompt, then open System Preferences to the relevant pane, then exit, telling the user to try again after interactively enabling access.
    # Returns:
    #   Only returns if access is already enabled; throws an error otherwise.
    # Example:
    #   try 
    #       my ensureAssistiveAccess()
    #   on error
    #       # Enabling failed (10.8-: user didn't provide admin password; 10.9: user may or may not have authorized
    #       # this script's app as instructed by the prompt, but either way the script must be restarted.
    #       # Exit quietly, relying on the prompt to have provided sufficient information.
    #       return
    #   end try
    on ensureAssistiveAccess()
        local ok, isPreMavericks, verOs, verMajor, verMinor, appName, errMsg
        # Determine if access is currently enabled.
        tell application "System Events" to set ok to UI elements enabled
        if not ok then
            # See if we're running 10.8 or below
            set {orgTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {"."}}
            set verOs to system version of (system info)
            set verMajor to first text item of verOs as number
            set verMinor to second text item of verOs as number
            set AppleScript's text item delimiters to orgTIDs
            set isPreMavericks to verMajor ≤ 10 and verMinor < 9
            if isPreMavericks then # 10.8-: we can try to turn it on ourselves, which will prompt for authorization
                try
                    # Try to turn it on - will prompt for authorization via admin credentials.
                    tell application "System Events"
                        set UI elements enabled to true
                        set ok to UI elements enabled # Check if the user actually provided the authorization.
                    end tell
                end try
            else # 10.9+: we cannot turn it on ourselves, it has to be enabled *interactively*, *per application*.
                # Try a dummy GUI scripting operation - which we know will fail - in the hope that this will
                # get the app at hand registered in System Preferences > Security & Privacy > Privacy > Accessibility.
                # ?? Does this work?
                try
                    tell application "System Events" to windows of process "SystemUIServer"
                end try
                set appName to name of current application
                if appName = "osascript" then set appName to "Terminal" # ?? how can we deal with other apps that invoke `osascript`, such as Alfred?
                set errMsg to "You must turn on ACCESS FOR ASSISTIVE DEVICES for application '" & appName & "' (System Preferences > Security & Privacy > Privacy > Accessibility) first, then retry."
                try
                    display dialog errMsg & linefeed & linefeed & "Press OK to open System Preferences now; unlock, if necessary, then locate the application in the list and check it." with icon caution
                    # We only get here if the user didn't cancel.
                    # Open System Preferences and show the appropriate pane. (This is the best we can do in guiding the user - further guidance would require the very kind of assistive access we're trying to turn on.)
                    tell application "System Preferences"
                        activate
                        tell pane id "com.apple.preference.security"
                            reveal anchor "Privacy_Assistive"
                        end tell
                    end tell
                end try
            end if
        end if
        if not ok then
            if isPreMavericks then # This indicates that the authorization prompt was aborted; for 10.9+, errMsg was set above.         
                set errMsg to "You must turn on ACCESS FOR ASSISTIVE DEVICES first, via System Preferences > Accessibility > Enable access for assistive devices"
            end if
            error errMsg
        end if
    end ensureAssistiveAccess
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thanks so much, the clipboard parsing trick is really clever. [This linked script](https://gist.github.com/ZhimingWang/8764383) is what I have done so far—it works perfectly for both hyperlinks and unlinked bare URLs. However, I can't figure out the "send a copy-to-clipboard command to the active application" step. (Sorry I'm not familiar with AppleScript at all.) While it is not a problem—manually `Cmd+C` after selecting the text isn't a big deal, could you tell me how to actually implement the sending to clipboard thing? Thanks again. – 4ae1e1 Feb 02 '14 at 07:51
  • @KevinSayHi My pleasure - to give credit where credit is due, I've updated my answer with a reference to the SO answer that I originally got the get-RTF/HTML-as-source-text trick from. I've also updated my answer to show you how two ways to invoke the copy-to-clipboard command. Btw, I'm not sure you need the `using terms from ...` - isn't just the `tell application "Safari"` enough? – mklement0 Feb 02 '14 at 15:34
  • Thanks for the comprehensive answer :) – 4ae1e1 Feb 02 '14 at 22:27
0

It is annoying that this is not possible, but have you tried using (or is there a reason you aren't using) the right-click (control-click) contextual menu item "Add Link to Reading List" in Safari? I'm guessing you want to add the Service to any application, in which case, AFAIK, you're out of luck.

CRGreen
  • 3,406
  • 1
  • 14
  • 24
  • Yeah, I want to save links from emails or Chrome to Safari—in fact, I use Safari solely for the reading list and reader features. – 4ae1e1 Feb 02 '14 at 06:46