I need to URL-encode a string in AppleScript - applescript

My script searches a website for songs, but when there are spaces it doesn't search, you have to add underscores. I was wondering if there was a way to replace my spaces with underscores.
Could you please use my current code below to show me how to do it?
set search to text returned of (display dialog "Enter song you wish to find" default answer "" buttons {"Search", "Cancel"} default button 1)
open location "http://www.mp3juices.com/search/" & search
end

Note: The solution no longer works as of Big Sur (macOS 11) - it sounds like a bug; do tell us if you have more information.
Try the following:
set search to text returned of (display dialog "Enter song you wish to find" default answer "" buttons {"Search", "Cancel"} default button 1)
do shell script "open 'http://www.mp3juices.com/search/'" & quoted form of search
end
What you need is URL encoding (i.e., encoding of a string for safe inclusion in a URL), which involves more than just replacing spaces.
The open command-line utility, thankfully, performs this encoding for you, so you can just pass it the string directly; you need do shell script to invoke open, and quoted form of ensures that the string is passed through unmodified (to be URI-encoded by open later).
As you'll see, the kind of URL encoding open performs replaces spaces with %20, not underscores, but that should still work.

mklement0's answer is correct about url encoding but mp3juices uses RESTful URLs (clean URLs). RESTful URLs want's to keep the URL human readable and you won't see/use typical hex values in your url presenting an ASCII number. A snake_case, as you have mentioned (is false), but it is pretty common to use an substitution for whitespaces (%20) (and other characters) in RESTful URLs. However the slug of an RESTful must be converted to RESTful's own RESTful encoding before it can be handled by standard URL encoding.
set search to text returned of (display dialog "Enter song you wish to find" default answer "" buttons {"Search", "Cancel"} default button 1)
set search to stringReplace(search, space, "-")
do shell script "open 'http://www.mp3juices.com/search/'" & quoted form of search
on stringReplace(theText, searchString, replaceString)
set {oldTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, searchString}
set textItems to every text item of theText
set AppleScript's text item delimiters to replaceString
set newText to textItems as string
set AppleScript's text item delimiters to oldTID
return newText
end stringReplace
EDIT: updated the code, unlike the question mentioned that spaces are converted to underscores, mp3juice uses hyphens as substitution for whitespaces.

An update on this, despite the fact that the answer is 3 years old, as I faced the same problem: on recent versions of macOS/OS X/Mac OS X (I think, 10.10 or later), you can use ASOC, the AppleScript/Objective-C bridge:
use framework "Foundation"
urlEncode("my search string with [{?#äöü or whatever characters")
on urlEncode(input)
tell current application's NSString to set rawUrl to stringWithString_(input)
set theEncodedURL to rawUrl's stringByAddingPercentEscapesUsingEncoding:4 -- 4 is NSUTF8StringEncoding
return theEncodedURL as Unicode text
end urlEncode
It should be noted that stringByAddingPercentEscapesUsingEncoding is deprecated, but it will take some time until it’s removed from macOS.

URL encoding in AppleScript
For a general use case (for me at the moment to pass any ASCII url containing chars like #, &, ß, ö to the bit.ly API), I stumbled upon a nice code snippet that instantly added full support to my ShortURL clipboard pasting shortcut. Here's a quote from source:
i was looking for a quick and dirty way to encode some data to pass to a url via POST or GET with applescript and Internet Explorer, there were a few OSAXen which have that ability, but i didn't feel like installing anything, so i wrote this thing (works with standard ascii characters, characters above ascii 127 may run into character set issues see: applescript for converting macroman to windows-1252 encoding)
Notes
Double encoding should be duly noted.
Not tested on non-ASCII URLs.
Tested on OS X 10.8.5.
Code
on urlencode(theText)
set theTextEnc to ""
repeat with eachChar in characters of theText
set useChar to eachChar
set eachCharNum to ASCII number of eachChar
if eachCharNum = 32 then
set useChar to "+"
else if (eachCharNum ≠ 42) and (eachCharNum ≠ 95) and (eachCharNum < 45 or eachCharNum > 46) and (eachCharNum < 48 or eachCharNum > 57) and (eachCharNum < 65 or eachCharNum > 90) and (eachCharNum < 97 or eachCharNum > 122) then
set firstDig to round (eachCharNum / 16) rounding down
set secondDig to eachCharNum mod 16
if firstDig > 9 then
set aNum to firstDig + 55
set firstDig to ASCII character aNum
end if
if secondDig > 9 then
set aNum to secondDig + 55
set secondDig to ASCII character aNum
end if
set numHex to ("%" & (firstDig as string) & (secondDig as string)) as string
set useChar to numHex
end if
set theTextEnc to theTextEnc & useChar as string
end repeat
return theTextEnc
end urlencode

If you need to get the URL as a string (not just feed it into open which does a nifty job of encoding for you) and you're not above using a little Automator, you can throw some JavaScript into your AppleScript:
encodeURIComponent is a built in JavaScript function - it is a complete solution for encoding components of URIs.
For copy/pasters, here are all three scripts in the above Automator chain:
on run {input, parameters}
return text returned of (display dialog "Enter song you wish to find" default answer "" buttons {"Search", "Cancel"} default button 1)
end run
function run(input, parameters) {
return encodeURIComponent(input);
}
on run {input, parameters}
display dialog "http://www.mp3juices.com/search/" & input buttons {"okay!"} default button 1
end run

I was hunting around for URL encoding and decoding and came across this helpful link.
Which you can use like so:
set theurl to "https://twitter.com/zackshapiro?format=json"
do shell script "php -r 'echo urlencode(\"" & theurl & "\");'"
# gives me "https%3A%2F%2Ftwitter.com%2Fzackshapiro%3Fformat%3Djson"
set theurl to "https%3A%2F%2Ftwitter.com%2Fzackshapiro%3Fformat%3Djson"
return do shell script "php -r 'echo urldecode(\"" & theurl & "\");'"
# gives me "https://twitter.com/zackshapiro?format=json"
Or as functions:
on encode(str)
do shell script "php -r 'echo urlencode(\"" & str & "\");'"
end encode
on decode(str)
do shell script "php -r 'echo urldecode(\"" & str & "\");'"
end decode

Just so it's said, AppleScriptObjC allows us to use NSString to do the encoding. The script is complicated by the fact that different parts of the URL allow different characters (all of which I've added options for) but in most cases the 'query' option will be used.
See NSCharacterSet's dev page (the section called "Getting Character Sets for URL Encoding") for descriptions of the various URL parts.
use AppleScript version "2.4" -- Yosemite 10.10 or later
use framework "Foundation"
property NSString : class "NSString"
property NSCharacterSet : class "NSCharacterSet"
-- example usage
my percentEncode:"some text" ofType:"query"
on percentEncode:someText ofType:encodeType
set unencodedString to NSString's stringWithString:someText
set allowedCharSet to my charSetForEncodeType:encodeType
set encodedString to unencodedString's stringByAddingPercentEncodingWithAllowedCharacters:allowedCharSet
return encodedString as text
end percentEncode:ofType:
on charSetForEncodeType:encodeType
if encodeType is "path" then
return NSCharacterSet's URLPathAllowedCharacterSet()
else if encodeType is "query" then
return NSCharacterSet's URLQueryAllowedCharacterSet()
else if encodeType is "fragment" then
return NSCharacterSet's URLFragmentAllowedCharacterSet()
else if encodeType is "host" then
return NSCharacterSet's URLHostAllowedCharacterSet()
else if encodeType is "user" then
return NSCharacterSet's URLUserAllowedCharacterSet()
else if encodeType is "password" then
return NSCharacterSet's URLPasswordAllowedCharacterSet()
else
return missing value
end if
end charSetForEncodeType:

The Python Approach:
Find your python3 path (which python3) or if you don't have it, install using brew or miniconda
Now try this:
python_path = /path/to/python3
set search_query to "testy test"
tell application "Google Chrome"
set win to make new window
open location "https://www.google.com/search?q=" & url_encode(q)
end tell
on url_encode(input)
return (do shell script "echo " & input & " | " & python_path & " -c \"import urllib.parse, sys; print(urllib.parse.quote(sys.stdin.read()))\"
")
end url_encode
credits to #Murphy https://stackoverflow.com/a/56321886

Related

Get data from webpage via Apple script

I'm trying to get the data of Crypto name, Price, and % from the webpage https://www.worldcoinindex.com/
How can i get the % column value via the following scripts?
set theHtml to do shell script "curl -s " & quoted form of "https://www.worldcoinindex.com"
set text item delimiters to {"<tbody>", "</tbody>"}
set tableContents to theHtml's text item 2 # item 2 is the body of the price table
set text item delimiters to {"<h2>"} # site uses new h2 for each currency
set tableChunks to tableContents's text items 2 thru -1
set pasteStr to ""
repeat with aChunk in tableChunks
set text item delimiters to "><span>$ </span><span class=\"span\">"
tell aChunk's text item 1 to set {theSymbol, thePrice} to {first word, last word}
set pasteStr to pasteStr & theSymbol & tab & thePrice & return
end repeat
set the clipboard to pasteStr
Here is an alternate way for your consideration:
Note that this requires Allow JavaScript from Apple Events to be checked on the hidden Develop menu in Safari.
To unhide the hidden Develop menu:
Safari > Preferences… > Advanced > [√] Show Develop menu in menu bar
Example AppleScript code:
tell application "Safari"
make new document ¬
with properties {URL:"https://www.worldcoinindex.com"}
my waitForSafariPageToFinishLoading()
tell front document
set tickerList to {}
set lastPriceList to {}
set percentageList to {}
repeat with i from 0 to 99
set ticker to ¬
do JavaScript ¬
"document.getElementsByClassName('ticker')[" & i & "].innerText;"
copy words of ticker as text to end of tickerList
set lastPrice to ¬
do JavaScript ¬
"document.getElementsByClassName('number pricekoers lastprice')[" & i & "].innerText;"
copy lastPrice to end of lastPriceList
set percentage to ¬
do JavaScript ¬
"document.getElementsByClassName('percentage')[" & i & "].innerText;"
copy percentage to end of percentageList
end repeat
end tell
end tell
set tempListItem to {}
set groupedItemsList to {}
repeat with i from 1 to 100
copy item i of tickerList to end of tempListItem
copy item i of lastPriceList to end of tempListItem
copy item i of percentageList to end of tempListItem
copy tempListItem to end of groupedItemsList
set tempListItem to {}
end repeat
set tabDelimitatedListAsText to ""
repeat with anItem in groupedItemsList
set {TID, AppleScript's text item delimiters} to ¬
{AppleScript's text item delimiters, tab}
set thisItem to text of anItem as text
set AppleScript's text item delimiters to TID
set tabDelimitatedListAsText to ¬
tabDelimitatedListAsText & thisItem & linefeed
end repeat
set the clipboard to tabDelimitatedListAsText
on waitForSafariPageToFinishLoading()
-- # Wait for page to finish loading in Safari.
-- # This works in macOS Catalina and
-- # macOS Big Sur and may need adjusting
-- # for other versions of macOS.
tell application "System Events" to repeat until ¬
exists (buttons of groups of toolbar 1 of window 1 of ¬
process "Safari" whose name = "Reload this page")
delay 0.5
end repeat
end waitForSafariPageToFinishLoading
Note: The example AppleScript code is just that and sans any included error handling does not contain any additional error handling as may be appropriate. The onus is upon the user to add any error handling as may be appropriate, needed or wanted. Have a look at the try statement and error statement in the AppleScript Language Guide. See also, Working with Errors. Additionally, the use of the delay command may be necessary between events where appropriate, e.g. delay 0.5, with the value of the delay set appropriately.

Simple way to implement a string interpolation in AppleScript

I want to implement a string interpolation in AppleScript, similar to the basic python implementation. For example in python:
print('Hello {}'.format('earth')) #> Hello World
In AppleScript, how do I implement the format handler?
to format(theString, {tokens})
-- TODO
end format
log format("Hello {}", {"Apple"}) -- # Hello Apple
The python syntax and the AppleScript/ObjC syntax are not compatible.
If you are talking about only one argument there is a simple solution with help of NSString's stringWithFormat
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions
to format(theString, tokens)
return (current application's NSString's stringWithFormat_(theString, tokens)) as text
end format
log format("Hello %#", "Apple") -- # Hello Apple
For more arguments you have to use an if - else clause to convert the argument array to the ObjC va_list
to format(theString, tokens)
set numberOfArguments to count tokens
if numberOfArguments = 1 then
return (current application's NSString's stringWithFormat_(theString, item 1 of tokens)) as text
else if numberOfArguments = 2 then
return (current application's NSString's stringWithFormat_(theString, item 1 of tokens, item 2 of tokens)) as text
else if numberOfArguments = 3 then
return (current application's NSString's stringWithFormat_(theString, item 1 of tokens, item 2 of tokens, item 3 of tokens)) as text
else
error "Invalid number of arguments"
end if
end format
log format("Hello I'm %# and I'm %# years old", {"John", 25}) -- # Hello I'm John and I'm 25 years old
I wrote a simple implementation using sed
to format(theString as text, theTokens as list)
if class of theTokens is text then set theTokens to {theTokens}
set builtString to theString
repeat with nextToken in theTokens
set builtString to do shell script "echo '" & builtString & "' | sed 's/{}/" & nextToken & "/'"
end repeat
return builtString
end format
I have tested it with only 2 scenarios, I'm sure there are more I haven't covered:
format("Hello {}", "baby") -- # Hello baby
"Hello {}, how are you {}", {"baby", "mom"} -- # Hellow baby, how are you mom

Apple Script - How to I append the clipboard with formatting preserved to a note in Apple Notes?

I've created a script that appends whatever's on my clipboard to an Apple note. However, the formatting of the appended text is not preserved, not even the line formatting. How to I append the clipboard to the note while preserving the LINE formatting of the clipboard? I don't care as much about the other formatting, although it would be nice to preserve it if possible.
Also, I would like the text to be appended as a new line with a line break in between the pre-existing and appended text, and the text size of the entire note—including the pre-existing and appended text—to be 18 points.
set AppendText to (the clipboard)
tell application "Notes"
tell account "iCloud"
tell folder "Clipboard"
set OriginalText to the body of note 1 -- the contents of the notes are encoded in HTML
end tell
end tell
end tell
tell application "Notes"
tell account "iCloud"
tell folder "Clipboard"
set body of note 1 to {"<div style=\"font-size: 18px\">" & OriginalText & "<br>" & AppendText & "</div>"}
end tell
end tell
end tell
Suppose that the pre-existing text of the note is
Original text line 1
Original text line 2
Original text line 3
and that the text that needs to be appended is
Append text line 1
Append text line 2
Append text line 3
When I run the script the text of the note is set to
Original text line 1
Original text line 2
Original text line 3
Append text line 1 Append text line 2 Append text line 3
Whereas I want it to be
Original text line 1
Original text line 2
Original text line 3
Append text line 1
Append text line 2
Append text line 3
Since the body of the note is HTML, one solution would be to use the textutil utility to convert the append text before adding (the Notes application handles merging the HTML), for example:
set appendText to (the clipboard)
set convertedText to (do shell script "echo " & quoted form of appendText & " | textutil -convert html -excludedelements '(p)' -stdin -stdout")
tell application "Notes"
tell folder "Whatever" -- example
set originalText to body of note 1
set body of note 1 to originalText & convertedText
end tell
end tell
Thanks to red_menace and user3439894, I've completed the script. Here it is.
set appendText to (the clipboard)
set convertedText to (do shell script "echo " & quoted form of appendText & ¬
" | textutil -convert html -fontsize 18 -excludedelements '(p)' -stdin -stdout")
tell application "Notes"
tell account "iCloud"
tell folder "Clipboard"
set originalText to the body of note 1
if originalText as string is "" then
set body of note 1 to {"<div style=\"font-size: 18px\">" & convertedText & "</div>"}
else
set body of note 1 to {"<div style=\"font-size: 18px\">" & originalText & ¬
"</div><div><span style=\"font-size: 18px\"><br></span></div> <div style=\"font-size: 18px\">" & ¬
convertedText & "</div>"}
end if
end tell
end tell
end tell

Get date added of Finder items in Applescript

I can get files by date modified using this bit of code:
get (files of entire contents of folder "Macintosh HD:General Music:05 Reggae:" whose modification date is less than ((current date)) - modDate * days)
but I can't seem to get their date added (nor is it listed in Applescript dictionary for Finder that I can see). This is weird, 'cause I can do a smart folder that uses this property.
Any idea on how to get files who were added within 15 days? Otherwise I'm doing loads of weird stuff with GUI at the moment and I'd like to automate it further.
Thanks
Tardy
You can search Spotlight's metadata with the mdfind command, use the kMDItemDateAdded key:
set _15daysAgo to -15 * days -- number of seconds
set tFolder to quoted form of POSIX path of "Macintosh HD:General Music:05 Reggae:"
-- find files, not folders
do shell script "mdfind -onlyin " & tFolder & " 'kMDItemDateAdded>$time.now(" & _15daysAgo & ") && ! kMDItemContentType == public.folder'"
set tFiles to paragraphs of the result
repeat with i in tFiles
tell i to set contents to i as POSIX file as alias
end repeat
tFiles -- list of files who were added within 15 days
Or, use the methods of the NSFileManager Class to get the NSURLAddedToDirectoryDateKey of the files (require Yosemite or El Capitan),
Here's the AppleScript:
set _15daysAgo to -15 * days -- number of seconds
set f to POSIX path of "Macintosh HD:General Music:05 Reggae:"
do shell script "/usr/bin/python -c 'import sys; from Foundation import NSFileManager, NSURL, NSDate, NSDirectoryEnumerationSkipsHiddenFiles
def procFolder(tDir):
p = dfM.contentsOfDirectoryAtURL_includingPropertiesForKeys_options_error_(tDir, myKeys, NSDirectoryEnumerationSkipsHiddenFiles, None)[0]
for f in p:
myDict, error=f.resourceValuesForKeys_error_(myKeys, None)
if error is None:
if (myDict.get(\"NSURLIsDirectoryKey\")): procFolder(f)
elif (myDict.get(\"NSURLAddedToDirectoryDateKey\").compare_(d) == 1):
print f.path().encode(\"utf8\")
fold=NSURL.fileURLWithPath_isDirectory_(sys.argv[1].decode(\"utf8\"), True)
dfM=NSFileManager.defaultManager()
d=NSDate.dateWithTimeIntervalSinceNow_(" & _15daysAgo & ")
myKeys=[\"NSURLIsDirectoryKey\", \"NSURLAddedToDirectoryDateKey\"]
procFolder(fold)' " & f
set tFiles to paragraphs of the result
repeat with i in tFiles
tell i to set contents to i as POSIX file as alias
end repeat
tFiles -- list of files who were added within 15 days

Cannot pass handler parameter to code. Newbie

Background: I am trying to pass caller information from a telephony application (Phone Amego) to my Samsung tv using its AllShare feature. In order to do so I have to send a soap message with the caller information. Phone Amego provides an easy way to assign an applescript to call events. But, I have no programming experience whatsoever!
Sofar I have succeeded in making a script which reads a soap message, updates the required fields and sends it to my tv. Perfect, at least the result, the code may not be perfect but it works. Here is the code.
set callerID_string to "John Doe : 1-917-123-4567"
set AppleScript's text item delimiters to {":"}
set pieces to text items of callerID_string
set callerID_name to item 1 of pieces
set callerID_number to item 2 of pieces
set AppleScript's text item delimiters to {""}
set myDate to do shell script "date '+%d.%m.%Y'"
set myTime to do shell script "date '+%T'"
set total_Length to (length of callerID_name) + (length of callerID_number) + 780
set search_strings to {"Content-Length: 796", "2013-01-01", "00:00:00", "Mike", "777-777-7777"}
set replace_strings to {"Content-Length:" & total_Length, myDate, myTime, callerID_name, callerID_number}
tell application "Finder"
set theFile to alias "Macintosh HD:Users:Marc:Desktop:IncCallMBsTemplate.txt"
open for access theFile
set fileRef to open for access (theFile as alias)
set fileContents to (read fileRef)
close access theFile
end tell
set the clipboard to fileContents
repeat with i from 1 to (count search_strings)
set the_string to the clipboard
set the_string to my snr(the_string, item i of search_strings, item i of replace_strings)
end repeat
on snr(the_string, search_string, replace_string)
tell (a reference to my text item delimiters)
set {old_atid, contents} to {contents, search_string}
set {the_string, contents} to {the_string's text items, replace_string}
set {the_string, contents} to {"" & the_string, old_atid}
end tell
set the clipboard to the_string
-- Create a file handler for the file to write to.
set myFile to (open for access alias "Macintosh HD:Users:Marc:Desktop:Test.txt" with write permission)
try
-- Delete current contents of the file
set eof myFile to 0
-- Write to file
write the_string to myFile as «class utf8»
end try
-- Close the file
close access myFile
end snr
set cmd to "/Usr/bin/nc 10.0.1.7 52235 < /Users/Marc/Desktop/Test.txt"
do shell script cmd
Problem: In the script above I have set a value to the variable callerID_string, but I should obtain it from Phone Amego through the handler call_from(callerID_string). But whatever I try, I cannot pass that callerID_string to my code. It consists of 2 parts namely the caller's name and number. The code should start like:
on call_from(callerID_string)
Any help would be highly appreciated.
I assume your script is running with the other app at the same time and it needs that handler and therefore a little re-designing so we can call the (former main) code as a subroutine.
I'm not sure if I understood the situation correctly but here it is anyway:
I added the on call_from... handler which calls the rest of the code as subroutine (myAction).
First commented line in code can be uncommented and used to test-run it with AppleScript Editor. Remove the line when not needed anymore.
testFilePath is a property and globally visible. Also, I changed the hardcoded file-path-stuff so it finds the path to the desktop.
property testFilePath : ""
-- my myAction("John Doe : 1-917-123-4567")
on call_from(callerID_string)
my myAction(callerID_string)
end call_from
on myAction(CIS)
if CIS is "" then
tell me to activate
display dialog "Caller ID is empty." buttons {"Quit"} default button 1 with icon 0
return
end if
set pathToDesktop to (path to desktop) as text
set testFilePath to pathToDesktop & "Test.txt"
set callerID_string to CIS -- "John Doe : 1-917-123-4567"
set lastTextItemDelimeter to AppleScript's text item delimiters
set AppleScript's text item delimiters to {" : "}
set pieces to text items of callerID_string
set callerID_name to item 1 of pieces
set callerID_number to item 2 of pieces
set AppleScript's text item delimiters to {""} -- or lastTextItemDelimeter if you want to change it back to last
set myDate to do shell script "date '+%d.%m.%Y'"
set myTime to do shell script "date '+%T'"
set total_Length to (length of callerID_name) + (length of callerID_number) + 780
set search_strings to {"Content-Length: 796", "2013-01-01", "00:00:00", "Mike", "777-777-7777"}
set replace_strings to {"Content-Length:" & total_Length, myDate, myTime, callerID_name, callerID_number}
set templateFile to pathToDesktop & "IncCallMBsTemplate.txt"
tell application "Finder"
set theFile to templateFile as alias -- Macintosh HD:Users:Marc:Desktop:IncCallMBsTemplate.txt"
open for access theFile
set fileRef to open for access (theFile as alias)
set fileContents to (read fileRef)
close access theFile
end tell
set the clipboard to fileContents
repeat with i from 1 to (count search_strings)
set the_string to the clipboard
set the_string to my snr(the_string, item i of search_strings, item i of replace_strings)
end repeat
set cmd to "/usr/bin/nc 10.0.1.7 52235 < " & quoted form of (POSIX path of testFilePath)
do shell script cmd
end myAction
on snr(the_string, search_string, replace_string)
tell (a reference to my text item delimiters)
set {old_atid, contents} to {contents, search_string}
set {the_string, contents} to {the_string's text items, replace_string}
set {the_string, contents} to {"" & the_string, old_atid}
end tell
set the clipboard to the_string
-- Create a file handler for the file to write to.
set myFile to (open for access (testFilePath as alias) with write permission)
try
-- Delete current contents of the file
set eof myFile to 0
-- Write to file
write the_string to myFile as «class utf8»
on error the error_message number the error_number
-- display dialog "Error: " & the error_number & ". " & the error_message buttons {"Cancel"} default button 1
log "Error: " & the error_number & ". " & the error_message
end try
-- Close the file
close access myFile
end snr

Resources