Applescript: GUI Programming for recursively printing to PDF for Appleworks - pdf

The Goal: thousands of old Clarisworks and Appleworks documents need to become PDFs.
The Problem: Scripting Pages is not an option; it doesn't preserve formatting reliably. It must be Appleworks. This script works, when it works; but it skips files for reasons I cannot identify.
set appleworksFolder to choose folder
tell application "Finder"
set folderItems to (files of entire contents of appleworksFolder)
repeat with I from 1 to number of items in folderItems
set the_doc to item I of folderItems
set doc_name to name of the_doc as text
(* Some files are missing metatags and try to save as "Untitled Document",
this block ensures a file name is unique, later *)
tell application "Finder"
set the clipboard to doc_name & ".pdf"
end tell
(* Each file exists in a folder with a path.txt file that will later
be used to put the file back where it was originally stored prior
to this conversion process *)
if name of the_doc is not "path.txt" then
try
tell application "Finder"
(* Many files no longer have name extensions and appear as UNIX
executables if not repaired *)
try
set nmex to name extension of the_doc as text
on error
set nmex to "ok"
end try
if nmex is not "cwk" or "CWK" then
set the_doc_str to the_doc as text
set doc_path to POSIX path of the_doc_str
do shell script "mv '" & doc_path & "' " & "'" & doc_path & ".cwk'"
end if
delay 1
(* In case Appleworks hangs or otherwise bungs up, I force-quit
it at the end of the script; this ensures its closed before
it tries to open the next file *)
if (application process "Appleworks 6" of application "System Events" exists) then
do shell script "killall 'LaunchCFMApp'"
delay 1
end if
tell application "AppleWorks 6"
open the_doc
end tell
(* Some of the documents are huge, this delay gives the app time to load
since this is all GUI-scripted *)
delay 5
tell application process "Appleworks 6" of application "System Events"
(* This is where I think I am encountering problems; there are two
possible warnings that may or may not appear on opening the doc;
that Appleworks needs to append a version number to the file (if
its old) or that the file may be damaged and thus would need to be
skipped. I get system beeps sometimes in this part, but I don't know
why! *)
if (button "OK" of window 1 of application process "AppleWorks 6" of application "System Events" exists) then
delay 0.5
keystroke return
delay 0.5
end if
delay 2
if (button "OK" of window 1 of application process "AppleWorks 6" of application "System Events" exists) then
delay 0.5
keystroke return
delay 0.5
end if
delay 2
(* If the document loads, the Appleworks welcome pane won't be there;
this part of the script works flawlessly, when it happens. Sometimes
documents are outside of print margins, hence the press-ok-button
conditional *)
if not (window "Starting Points" of application process "AppleWorks 6" of application "System Events" exists) then
tell application process "Appleworks 6" of application "System Events"
keystroke "p" using command down
delay 1
click menu button "PDF" of window "Print" of application process "AppleWorks 6" of application "System Events"
delay 1
click menu item "Save as PDF…" of menu "PDF" of menu button "PDF" of window "Print" of application process "AppleWorks 6" of application "System Events"
delay 1
keystroke "v" using command down
click button "Save" of window "Save" of application process "AppleWorks 6" of application "System Events"
delay 8
keystroke "w" using command down
delay 0.5
if (button 1 of window 1 of application process "AppleWorks 6" of application "System Events" exists) then
delay 0.5
keystroke "d" using command down
delay 0.5
end if
delay 0.5
end tell
end if
do shell script "killall 'LaunchCFMApp'"
delay 3
end tell
end tell
end try
end if
end repeat
end tell
I want to this baby to just run through a couple of thousands files over a weekend and create PDFs, but each time I run it overnight I find a few hundred correctly processed documents, hundreds or thousands of skipped documents, and often a print dialogue for the script itself, which obviously comes from using Command+P outside of an Appleworks context. I'm an Applescript noob, to be sure, this has been driving me nuts for weeks!

This is not really a solution to your problem, as it is highly specific and tailored, but I have some possibly useful tips:
You don't need to close the "Starting Points" window. It will be dismissed when you tell AppleWorks to open a document.
Try to use as few simulated keystrokes as possible. For instance, instead of simulating cmd+P to open the Print dialog, it is better to simulate a click on the application's Print… command inside its File menu:
click menu item "Print…" of menu "File" of menu bar 1 of application process "AppleWorks 6"
and instead of performing a cmd+W to close the document, the proper way is to do
tell application "AppleWorks 6" to close front document saving no
If you do need to simulate keystrokes, ensure that they are received by the correct application, by activating it just before the keystroke command with a tell app "AppleWorks 6" to activate, just in case it wasn't the frontmost app at that point.
When checking for additional dialog windows, it's a bad idea to use if (button 1 of window 1 of (*...*) exists), because on my machine, for instance, checking this while the "Print" dialog was open caused the script to hang for minutes (possibly for good), and because it's always advisable to address a window by name rather than number (since window 1 is simply the frontmost window, and that can quickly become another). So, it's better to check if a window named such and such exists.
In order to process thousands of files where you expect errors to happen, you need to restructure your code and rethink the error handling. For instance, you could log error information to a text file in order to see which files have been skipped and why. To separate program logic from the error handling and details, you could use handlers, so your main loop could look like this:
set logfile to alias "Macintosh HD:Users:user:Desktop:errors.log"
open for access logfile with write permission
repeat with the_doc in folderItems
try
open_with_appleworks(the_doc)
print_to_pdf()
close_document_and_quit_appleworks()
on error error_message
write error_message & "\n" to logfile
close_document_and_quit_appleworks()
end try
end repeat
close access logfile
In a similar way, you can log success messages, too. In the single handlers, then, you are supposed to specify details about how exactly to do every step. They can also provide some error information to be logged:
to open_with_appleworks(the_doc)
tell app "AppleWorks 6"
try
(* do your stuff here *)
on error err_msg
(* re-signal the error to main loop! *)
error "Failed to open " & (the_doc as text) & err_msg
end try
end tell
end open_with_appleworks
Hard-coded delays slow down your script considerably: while only some of your documents might take a few seconds to open, your script will always wait 5 seconds – even if the document opens instantaneously. It's better to have a loop check whether the document has opened yet:
repeat while not (exists front document)
-- just wait
-- or, delay 0.1 -- if you really want to
end repeat
The same holds for nearly all your other delays, most of them can be avoided, as they will be much shorter or longer in practice. Instead of delaying half a second, hoping that the warning or dialog will have appeared by then, it's better to check whether it's there or not.
(one last tip and then I'll be quiet) Unfortunately, you seem to already have gone through the trouble of copying all the files to be processed into one folder, remembering in some "path.txt" where they came from... This can be avoided if the files are in sub-folders of some folder and can be found by a filter: You just create a "smsart folder" with that specific search (example), then say in your AppleScript set theFiles to choose file with multiple selections allowed, then select all files in that particular smart folder, and you're good. The script can then easily find out where a file f is by asking application "Finder" to get parent of f (that will return a Finder file reference, which can be easily converted to an alias and to a POSIX file address). Then, since you know the name and location of the PDF file generated by "printing" the document, you can move that PDF to the folder where its source document is.

Related

How to click on this webpage button with AppleScript

I am having trouble clicking on the "search" button on a particular website. The website is a subscription service, so I am attaching a picture of the page pulled up in "inspect" mode as well as my code.
My code:
set myURL to "https://www.uptodate.com/contents/search"
tell application "Safari"
activate
make new document with properties {URL:myURL}
end tell
tell application "System Events"
repeat until exists (UI elements of groups of toolbar 1 of window 1 of application process "Safari" whose name = "Reload this page")
delay 0.3
end repeat
end tell
inputByID("tbSearch", "myVar")
clickClassName("newsearch-submit", 0)
###InputByID###
to inputByID(theId, theValue)
tell application "Safari"
do JavaScript "document.getElementById('" & theId & "').value ='" & theValue & "';" in document 1
end tell
end inputByID
###ClickByClass###
to clickClassName(theClassName, elementnum)
tell application "Safari"
do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
end tell
end clickClassName
As an alternative, you can use UI Scripting, where System Events preforms some keystrokes.
The following example AppleScript code works for me in macOS High Sierra1:
set myURL to "https://www.uptodate.com/contents/search"
tell application "Safari"
activate
make new document with properties {URL:myURL}
end tell
tell application "System Events"
repeat until (accessibility description of ¬
button 1 of UI element 1 of every group of toolbar 1 of window 1 of ¬
process "Safari" whose name = "Reload this page") contains "Reload this page"
delay 0.5
end repeat
end tell
tell application "Safari"
do JavaScript "document.getElementById('tbSearch').value ='PPE';" in document 1
end tell
tell application "System Events"
keystroke space
key code 51 -- # Press the delete key to remove the space.
keystroke return
end tell
1 NOTE: For macOS Mojave and later, change UI element 1 to UI element 2 in the repeat loop above.
Note: The example AppleScript code is just that and does not contain any 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.
I played around with this and of course not having a subscription cannot truly test it but perhaps you can see if this works for you - it seemed to be working:
tell application "Safari"
--activate --can work with or w/o Safari in foreground
set myURL to "https://www.uptodate.com/contents/search"
make new document with properties {URL:myURL}
delay 5 -- just waits for (hopefully) the page to load
set d to do JavaScript "document.forms[0][0].value = 'joint pain'" in document 1
do JavaScript "document.forms['searchForm'].submit();" in document 1
end tell

How to use AppleScript to search Safari's "smart" dropdown menu

I have a code that opens Safari to bing, inserts something to search, and searches it. However, I am trying to get the script to input the search THEN go down and search the autofill results rather than the actual search word. So far, I have been unable to do it. Here is a breakdown:
set theURL to "https://www.bing.com"
tell application "Safari" to make new document with properties {URL:theURL}
tell application "System Events"
repeat until exists (UI elements of groups of toolbar 1 of window 1 of application process "Safari" whose name = "Reload this page")
delay 0.5
end repeat
end tell
inputByID("sb_form_q", "coronavirus")
delay 0.5
clickID("sb_form_q") -- Here is where it breaks down. Trying to "click" the search bar after pasting so that System Events can 'arrow down'to the autofill results.
tell application "System Events"
key code 125
key code 125
end tell
delay (random number from 5 to 15)
clickID("sb_form_go")
delay 0.5
tell application "System Events"
delay 1.5
keystroke "w" using command down
end tell
to inputByID(theID, theValue)
tell application "Safari"
do JavaScript " document.getElementById('" & theID & "').value ='" & theValue & "';" in document 1
end tell
end inputByID
to clickID(theID)
tell application "Safari"
do JavaScript "document.getElementById('" & theID & "').click();" in document 1
end tell
end clickID
Ultimately, goal is to randomly search the autofill results based of the input. The code so far does not work and will just search the original input text.
I did some small edits in your code. Now this version of code, I test it more than 40 times. Not one lose. I write a lof of comments in the code. You can test it on your machine.
set theURL to "https://www.bing.com"
tell application "Safari"
activate
delay 0.3
-- 0th edit: I find "activate" is nessary for testing the code
make new document with properties {URL:theURL}
end tell
tell application "System Events"
tell process "Safari"
--1st edit: here "tell process "Safari"" is a must on my machine.
repeat until exists (button "Reload this page" of UI element 1 of group 2 of toolbar 1 of window 1)
delay 0.5
--2nd edit: the original line in your post "repeat until exists (UI elements of groups of toolbar 1 of window 1 of application process "Safari" whose name = "Reload this page")" doesn't work on my machine. If this doesn't work on your machine, you can change it back to your line.
end repeat
end tell
end tell
inputByID("sb_form_q", "coronavirus")
delay 0.3
--clickID("sb_form_q")
--delay 0.3
--3rd edit: I find this line is not nessary on my machine. Do you mean you use "arrow down" to show the autofill menu in your post? On my machine, after input "coronavirus", the dropdown autofill menu appears immediately and automatically. No need to use "arrow down" to show the dropdown menu.
tell application "System Events"
set randomNumber to random number from 1 to 8
repeat randomNumber times
key code 125
delay 0.3
end repeat
end tell
--delay (random number from 5 to 15)
clickID("sb_form_go")
--delay 0.5
--4th edit: actually i am not sure I understand what does the word "Randomly" mean in your post. I change the code here to select option in autofill randomly then execute the search.
tell application "System Events"
delay 2
keystroke "w" using command down
end tell
to inputByID(theID, theValue)
tell application "Safari"
do JavaScript " document.getElementById('" & theID & "').value ='" & theValue & "';" in document 1
end tell
end inputByID
to clickID(theID)
tell application "Safari"
do JavaScript "document.getElementById('" & theID & "').click();" in document 1
end tell
end clickID
My Safari version is "Version 10.1.2 (12603.3.8)" # MacOS 10.12.6. Let me know if it helps.

AppleScript Automation - Bulk uploading files in folder to a single-upload form

(First time with AppleScript...) I'm trying to bulk upload files from a local folder to a server via a single-upload form (legacy serverside software behind ddos wall, no control over it)
As I understand:
I can loop through each file in the filesystem.
With each file: Invoke "tell" Safari"
Invoke javascript to "click" a button by ID
file upload dialog, select the file to upload (?)
I'm having some trouble with syntax in implementing that...
(Also, if that's not the right/best approach, please provide a better one below!)
on run
tell application "Finder"
set mlist to (every file of folder "Macintosh HD:Users:username:filestouploadfolder") as alias list
repeat with this_file in mlist
tell application "Safari"
activate
do JavaScript "document.getElementById('selectToOpenFileDialog').click();" in document 1
choose file this_file
end tell
end repeat
end tell
return 0
end run
Hacked up a solution though it could probably be more elegant
on run
tell application "Finder"
set mfolder to "Macintosh HD:Users:yosun:png:"
set myFiles to name of every file of folder mfolder
end tell
repeat with aFile in myFiles
tell application "Safari"
activate
delay 1
do JavaScript "document.getElementById('addDeviceTargetUserView').click();" in document 1
delay 1
do JavaScript "document.getElementById('targetDimension').value=10;" in document 1
do JavaScript "document.getElementById('targetImgFile').click();" in document 1
end tell
tell application "System Events"
keystroke "G" using {command down, shift down}
delay 1
keystroke "~/png/" & aFile as string
delay 1
keystroke return
delay 1
keystroke return
delay 1
end tell
tell application "Safari"
activate
delay 1
do JavaScript "document.getElementById('AddDeviceTargetBtn').click();" in document 1
end tell
delay 10
end repeat
end run

File attachments are not attaching with applescript

I wrote a little applescript that attaches files in safari. Everything happens exactly how it would if I were attaching them manually, but for some unknown reason the attachments are not uploading even though they are being selected and submitted correctly.
I've spent a couple hours troubleshooting this and trying different variations with no success.
Here is the code that attaches them. I'm using cliclick in addition to applescript, which emulates mouse clicks.
set posix to "/Users/ea/Desktop/Guru/Deliverables" --set folder path
tell application "System Events"
keystroke "g" using {shift down, command down}
keystroke posix
delay 1
keystroke return
delay 2
keystroke "a" using {command down}
delay 5
do shell script "/usr/local/bin/cliclick m:381,339"
delay 3
do shell script "/usr/local/bin/cliclick m:818,590"
delay 2
do shell script "/usr/local/bin/cliclick tc:."
delay 2
end tell
files selected like they're supposed to
Upon clicking choose, nothing uploads.
Try This
tell application "Google Chrome" --Whatever your using
activate
set posix to "/Users/ea/Desktop/Guru/Deliverables/private" --are the files in here
tell application "System Events"
keystroke "g" using {shift down, command down}
keystroke posix
delay 1
keystroke return
delay 2
keystroke "a" using {command down}
delay 5
key code 36
end tell
end tell
Out of curiosity did you ever get it to work? and what do you mean by attaches files in safari? its select the files after you have the "file choosing window" open?
i know this is late, and someone seems to have already answered your question, but yeah you didn't respond to say if it worked or not, so heres something similar i created that might help:
to clickClassName(theClassName, elementnum)
tell application "Safari"
do JavaScript "document.getElementsByClassName('" & theClassName & "')[" & elementnum & "].click();" in document 1
end tell
end clickClassName
to getValueFromClass(theclass, num)
tell application "Safari"
tell document 1
set theFirstTableHTML to do JavaScript "\n document.getElementsByClassName('" & theclass & "')[" & num & "].value"
return theFirstTableHTML
end tell
end tell
end getValueFromClass
on run
choose file with prompt "Which folder would like average file size calculated?"
open {result}
end run
on open theimage
--tell application "Finder" to set xx to every file in item 1 of theimage
--display dialog "Hey! the file's alias is: " & (path of theimage as string)
--display dialog theimage
set filepath to POSIX path of theimage
tell application "Safari" to open location "https://upload.vstanced.com"
delay 2
clickClassName("btn btn-big white outline", 0)
tell application "System Events"
activate application "Safari"
delay 0.5
keystroke "g" using {shift down, command down} --open goto
set the clipboard to filepath
keystroke "v" using {command down}
delay 0.7
keystroke return -- enter goto text
delay 0.4
keystroke return --press enter on file
end tell
delay 1
clickClassName("btn btn-big green", 0)
set thedirectlink to ""
repeat 15 times
set thedirectlink to getValueFromClass("r2", 1)
delay 1
if thedirectlink is not equal to "" then
exit repeat
end if
end repeat
set the clipboard to thedirectlink
tell application "Safari" to close current tab of window 1
display notification "Upload complete" with title "VStanced Upload" subtitle thedirectlink sound name "blow"
end open
This is a droplet (in script editor save it with "file format" as "application"), when you drag an image onto it, it opens up an image hosting site, uses javascript to open the "file choosing window", and then pretty much does exactly what yours does.
After it selects and enters the file, it also selects "direct link" from a little "toggle button" thingy, copies the direct link to the clipboard, closes the tab, plays sound and shows a notification. if you end up trying it and it doesnt work, lengthen the delays. it does everything (i'm 99% sure). But if safari's not already open it will probably take too long to get to the page and that will mess everything up. Im also going to try to add parts that "wait" for the page to load.

How to programmatically generate a PDF from any document on OSX?

I'm working on a project for OSX where the user can pick a collection of documents (from any application) which I need to generate PDF's from. The standard Macintosh Print dialog has a PDF button which has a number of PDF-related commands including "Save as PDF...". However, I need to generate the PDF file without requiring user interactions. I ideally want this to work with any type of document.
Here's the options I've explored so far:
Automator actions. There's a PDF library for Automator but it provides actions for working with PDF files, not generating them. There's a Finder action for printing any file but only to a real printer.
AppleScript. Some applications have the ability to generate PDF files (for instance, if you send 'save doc in "test.pdf"' to Pages it will generate a PDF (but this only works for Pages - I need support for any type of document).
Custom Printer. I could create a virtual printer driver and then use the automator action but I don't like the idea of confusing the user with an extra printer in the print list.
My hope is that there's some way to interact with the active application as if the user was carrying out the following steps:
Do Cmd-P (opens the print dialog)
Click the "PDF" button
Select "Save as PDF..." (second item in menu)
Type in filename in save dialog
Click "Save"
If that's the best approach (is it?) then the real problem is: how do I send UI Events to an external application (keystrokes, mouse events, menu selections) ?
Update: Just to clarify one point: the documents I need to convert to PDF are documents that are created by other applications. For example, the user might pick a Word document or a Numbers spreadsheet or an OmniGraffle drawing or a Web Page. The common denominator is that each of these documents has an associated application and that application knows how to print it (and OSX knows how to render print output to a PDF file).
So, the samples at Cocoa Dev Central don't help because they're about generating a PDF from my application.
I think you could use applescript to open a document and then use applescript UI scripting to invoke print menu.
For example :
tell application "System Events"
tell window of process "Safari"
set foremost to true
keystroke "p" using {command down}
delay 3
click menu button "PDF" of sheet 2
click menu item "Save as PDF…" of menu 1 of menu button "PDF" of sheet 2
keystroke "my_test.file"
keystroke return
delay 10
end tell
end tell
Take a look at a program called CUPS-PDF
It is a virtual printer for OS X which does what the "Save As PDF" method does when print through your normal printer except every print job passed through it results in a pdf output.
Once you install it then you could create shell or AppleScripts using the lp command.
For example, once the virtual printer is setup you could print test.txt and have it automatically save as a pdf. To do this using an AppleScript you would use the following code:
do shell script "lp -d CUPS_PDF test.txt"
The CUPS-PDF app saves all output to /Users/Shared/CUPS-PDF. I am not sure if you can change that path but you could retrieve the file in your script and move it.
There are a few caveats though.
First, the lp command cannot print .doc files. I think there are some other third party apps which will allow you to do this though.
Second, the CUPS-PDF app shows in the Printer pane of System Preferences as having the hyphen in its name but CUPS shows the queue name as having an underscore. So, on the command line you need to refer to the CUPS queue name which is CUPS_PDF with an underscore.
Even if you don't find it very useful to build a script via the lp command
and still want to involve GUI scripting then having a virtual printer should save you some steps.
you could use cups like this
on open afile
set filename to name of (info for afile)
tell application "Finder"
set filepath to (container of (afile as alias)) as alias
end tell
set filepath to quoted form of POSIX path of filepath
set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to "."
set filename to text item 1 of filename
set AppleScript's text item delimiters to tid
set afile to quoted form of POSIX path of afile
do shell script "cupsfilter " & afile & " > " & filepath & filename & ".pdf"
end open
I have created an alias in bash for this:
convert2pdf() {
/System/Library/Printers/Libraries/convert -f "$1" -o "$2" -j "application/pdf"
}
I typed up the code below with the assistance of Automator (recording an action, and then dragging the specific action out of the "Watch Me Do" window in order to get the Applescript). If you want to print a PDF from an application other than Safari, you might have to run through the same process and tweak this Applescript around the Print dialogue, since each program might have a different Print GUI.
# Convert the current Safari window to a PDF
# by Sebastain Gallese
# props to the following for helping me get frontmost window
# http://stackoverflow.com/questions/480866/get-the-title-of-the-current-active-window- document-in-mac-os-x
global window_name
# This script works with Safari, you might have
# to tweak it to work with other applications
set myApplication to "Safari"
# You can name the PDF whatever you want
# Just make sure to delete it or move it or rename it
# Before running the script again
set myPDFName to "mynewpdfile"
tell application myApplication
activate
if the (count of windows) is not 0 then
set window_name to name of front window
end if
end tell
set timeoutSeconds to 2.0
set uiScript to "keystroke \"p\" using command down"
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "click menu button \"PDF\" of sheet 1 of window \"" & window_name & "\" of application process \"" & myApplication & "\""
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "click menu item 2 of menu 1 of menu button \"PDF\" of sheet 1 of window \"" & window_name & "\" of application process \"" & myApplication & "\""
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "keystroke \"" & myPDFName & "\""
my doWithTimeout(uiScript, timeoutSeconds)
set uiScript to "keystroke return"
my doWithTimeout(uiScript, timeoutSeconds)
on doWithTimeout(uiScript, timeoutSeconds)
set endDate to (current date) + timeoutSeconds
repeat
try
run script "tell application \"System Events\"
" & uiScript & "
end tell"
exit repeat
on error errorMessage
if ((current date) > endDate) then
error "Can not " & uiScript
end if
end try
end repeat
end doWithTimeout
"/System/Library/Printers/Libraries/./convert"
has been removed in later version of MacOS.
You can use sips now. AppleScript example:
do shell script: sips -s format pdf Filename.jpg --out Filename.pdf