Copying rich text from the property of one object to another - applescript

I'm trying to use AppleScript to find existing OmniFocus tasks and attach projects and contexts based on certain rules. This is working except that:
When I create the new task I'm trying to copy the note property directly. In the dictionary for OmniFocus it says that the note property is "rich text", but in the new task it seems to have become plain text (particularly, links in the text which I would like to remain are going away, but there is other style which is disappearing)
on set_project_and_context(the_task, the_project, the_context)
tell application "OmniFocus"
tell front document
set task_name to name of the_task
set task_note to note of the_task
set new_text to task_name & " ::" & the_project & " #" & the_context
set new_tasks to (parse tasks into with transport text new_text with as single task)
set new_task to item 1 of new_tasks
set due date of new_task to missing value
set note of new_task to task_note # <- HERE IS WHERE I'M TRYING TO COPY THE NOTE
delete the_task
end tell
end tell
end set_project_and_context
I'm an AppleScript newbie, so any help is appreciated ;)

You can't copy the note property directly because the new note would be plain text without all the styles and links.
To preserve formatting, you need to set the style property of every paragraph of the note property. I added a handler to set it.
Maybe this helps.
Code:
on set_project_and_context(the_task, the_project, the_context)
tell application "OmniFocus"
tell document 1
set task_name to name of the_task
set task_note to note of the_task
set new_text to task_name & " ::" & the_project & " #" & the_context
set new_tasks to (parse tasks into it with transport text new_text with as single task)
set new_task to item 1 of new_tasks
set due date of new_task to missing value
my SetNote(the_task, new_task) -- NEW HANDLER
delete the_task
end tell
end tell
end set_project_and_context
on SetNote(old_task, new_task)
using terms from application "OmniFocus"
set text of note of new_task to text of note of old_task
set lst_paragraphs to (every paragraph of note of old_task)
repeat with i from 1 to count lst_paragraphs
set style of paragraph i of note of new_task to (style of paragraph i of note of old_task)
end repeat
end using terms from
end SetNote

AppleScript itself doesn’t understand styled text, and there isn’t a standard way for apps to return text with style information attached, so:
set task_note to note of the_task
returns plain text only.
You need to tell the application to copy the rich text directly from one property to the other:
set note of new_task to note of the_task
Whether or not that command works will depend on how well the app’s scripting support is implemented; you just have to try it and see.

I actually solved this problem a bit unintuitively by creating the new task and assigning it's project and context to the existing task so that I don't need to re-create the note. There are probably better ways to fetch the project and context, though I couldn't find a better way to do it which involved finding nested projects / contexts
on set_project_and_context(the_task, project_text, context_text)
tell application "OmniFocus"
tell front document
set task_name to name of the_task
set new_text to task_name & " ::" & project_text & " #" & context_text
set new_tasks to (parse tasks into with transport text new_text with as single task)
set new_task to item 1 of new_tasks
set context of the_task to context of new_task
set the_project to containing project of new_task
set project_name to name of the_project
move the_task to end of tasks of the_project
delete new_task
end tell
end tell
end set_project_and_context

Related

AppleScript error: "Mail got an error: Can’t set text item delimiters to {"+", "#"}."

I have written an AppleScript that is activated by a mail rule whenever an email comes in that contains "+".
Why? I host my own mail server that allows for address tagging. What this means is that for example when I'm at a store and they ask for my email address, so they can email the receipt, I can give them my email address like this: whatmyemailnormallyis+nameofstore#domain.com. The applescript should then get the string between the "+" and "#" character, create a mailbox called "nameofstore" and move the message to it. Everything works fine except for I'm getting the following error:
"Mail got an error: Can’t set text item delimiters to {"+", "#"}."
This is my script:
tell application "Mail"
set unreadmessages to the first message of mailbox "INBOX" of account "Account"
set theEmail to extract address from sender of item 1 of unreadmessages
set mystring to theEmail
set text item delimiters to {"+", "#"}
set textlist to text items of mystring
set mylist to {}
repeat with i from 2 to count of textlist by 2
set end of mylist to item i of textlist
end repeat
get mylist
set mailboxName to mylist
set messageAccount to account of (mailbox of item 1 of unreadmessages)
set newMailbox to make new mailbox at (end of mailboxes of messageAccount) with properties {name:mailboxName}
repeat with eachMessage in unreadmessages
set mailbox of eachMessage to newMailbox
end repeat
end tell
When I run only the text extract portion of the script it works fine:
set mystring to "whatmyemailnormallyis+nameofstore#domain.com"
set text item delimiters to {"+", "#"}
set textlist to text items of mystring
set mylist to {}
repeat with i from 2 to count of textlist by 2
set end of mylist to item i of textlist
end repeat
get mylist
result:
{"nameofstore"}
Any help would be greatly appreciated. If anyone with better AppleScript skills than me can improve the script in other areas that would also be greatly appreciated.
This is an inheritance problem. The property (text item delimiters) is a property of the current application (AppleScript) instance, but it's being referenced unqualified inside the tell application block that directs commands to and enumerates properties from the target application, in this case Mail.
The temptation might be to set the text item delimiters outside the tell block, by moving it from its current line position to just before the block declaration. That's reasonable, but I think you've positioned it perfectly, as it's important (and good practice) to keep track of this property to ensure it's always appropriately set and, more significantly, never inappropriately not set. The most reliable way to do this, which also makes it easier to follow for other people, is to do as you've done, which is to set the property immediately prior to any statement where it exerts influence (namely, any time a list is coerced to text, or text is split into text items).
So, to avoid shuffling lines of code around, we need to be able to make it clear to the compiler that the property that we're referencing doesn't belong to application "Mail", but to the top-level scripting object, which will most typically be the current application. The three ways to do this are:
set AppleScript's text item delimiters to ...
set the current application's text item delimiters to ...
set my text item delimiters to ...
Stylistically, I favour the last option. However, my is not a synonym for current application nor for AppleScript, but rather a reference to the parent of your script. Unless the parent property is specifically declared in your script, then it will default to current application. However, there are reasons one might choose to assign a different value, in which case only the first or second of the above three options will be viable.
Here's a slight reworking of your script, which I'm afraid you'll have to test in lieu of my purchase of a new MacBook. I noticed some oddities in yours:
You obtain the first message in the inbox, but on the next line, reference item 1... of, what I imagine you thought would be a list of messages, but would in fact be a single message class object.
This single message object is the only message your script utilises to process the sender's email address. However, later on, you loop through, again, what you expect to be a collection of inbox messages, which, if it were, may not all have the same + tag.
You loopp through the text items generated by splitting the email address, and quite smartly start at index 2, and skip over every other item in the list. However, this list will only have three items in it, so there's never any looping to be done, and you can simply make use of text item 2.
In creating a new mailbox, you didn't first check to see if the mailbox already exists. I'm not sure whether Mail would throw an error, or silently ignore this. But I've redrafted the line to check first, and create if necessary.
Lastly, the final repeat loop is presently not necessary given unreadmessages is not a list (so might actually throw an error). So I removed the loop construct, but otherwise kept the line as it was. I'm not sure whether the mailbox property of a message is one that can be set, i.e. it might be read-only. If this is the case, that will throw an error, and you'll have to invoke the move command in order to move a message to a new mailbox. I may be wrong, though, and it may work just the way you intended.
tell application "Mail"
set firstInboxMessage to the first message in the inbox
set theEmail to extract address from sender of the firstInboxMessage
set my text item delimiters to {"+", "#"}
-- Since the script will trigger only when an email address
-- contains "+", we know text item 2 will always exist and
-- will always represent the slice of text we're after
set mailboxName to text item 2 of theEmail
set messageAccount to account of mailbox of the firstInboxMessage
tell the messageAccount to if the name of its mailboxes does not contain ¬
the mailboxName then make new mailbox at the end of its mailboxes ¬
with properties {name:mailboxName}
set newMailbox to the mailbox named mailboxName in the messageAccount
set the mailbox of the firstInboxMessage to the newMailbox
end tell
In reality, if you're invoking this script as a mail rule, you'll probably want to enclose this within the special handler that you can look up in the Mail scripting dictionary, called something like on receiving messages <messages> for mailbox rule <rule>

Is there an application type in applescript?

Is there an application type in AppleScript?
I have this handler:
on doHandler(theApplication)
set theApp to ("\"" & theApplication & "\"")
tell application theApp
set frontWindow to theApp's (window 1)
etc.
end tell
end doHandler
It is accessed as follows:
doHandler("TextEdit")
This produces the obvious error on theApp's (window 1).
So, what is the correct call?
This cannot work. The argument of tell application must be a literal string because the terminology is evaluated at compile time.
Apart from that the code doesn't work anyway because it expects that every application has an AppleScript dictionary containing a window property which is not the case.
All you need to do is eliminate the line set theApp to... and use the its keyword to set up the correct reference.
doHandler("TextEdit")
on doHandler(theApplication)
tell application theApplication
set frontWindow to its (window 1)
end tell
end doHandler
In the main script, application references are established at compile time, so you can't have a variable app name, but handlers aren't evaluated until run time.

Parse email for title and date, then create an todo in Things (applescript)

I'm new to Applescript and I've built this from code I've found online, and can't really get it to work.
What I want to do is the following
A rule in Apple Mail will trigger the script to find 2 text strings inside the body of the mail.
I want to extract two things from the e-mail
Due date (Återlämningsdatum)
Title of the book (Titel)
Then I want to create a todo in Things with the title of the book as name of the todo and the due date as due date.
The problem I run into now is that I don't get any data from the mail, just a empty todo is created.
Any ideas?
E-mail below
2017-03-22 18:43:55
MALMÖ STADSBIBLIOTEK
Stadsbiblioteket
Låntagarnummer: **********
Utlån
-------------------------
Titel: Ägg : recept & teknik / Tove Nilsson ; [fotografi: Charlie Drevstam]
Exemplarnummer: 3054550018
Återlämningsdatum: 2017-04-19
-------------------------
Antal utlånade material: 1
Code below
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
set subjectText to "Återlämningsdatum: "
set contentSearch to "Titel: "
using terms from application "Mail"
on perform mail action with messages theMessages for rule theRule
tell application "Mail"
set theContent to content
set theDate to my getFirstWordAfterSearchText(subjectText, theContent)
set theTitle to my getFirstWordAfterSearchText(contentSearch, theContent)
end tell
end perform mail action with messages
end using terms from
tell application "Things3"
set newToDo to make new to do
set name of newToDo to theTitle
set due date of newToDo to theDate
end tell
(*============== SUBROUTINES =================*)
on getFirstWordAfterSearchText(searchString, theText)
try
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, searchString}
set textItems to text items of theText
set AppleScript's text item delimiters to tids
return (first word of (item 2 of textItems))
on error theError
return ""
end try
end getFirstWordAfterSearchText
In my testing, your script returns the following values:
theDate: "2017"
theTitle: "Ägg"
So, I'm assuming you should perhaps get a blank to-do named "Ägg" ? If not, you could try:
tell application "Things3" to make new to do with properties ¬
{name: theTitle, due date: theDate}
(although I neither use nor own Things 3, so cannot test this for you).
Anyway, to address the original problem, the issue is in only asking your handler getFirstWordAfterSearchText to return the first word. I don't speak Swedish, but it looks to me that the title contains more than one word. The date most definitely does, as each word is separated by a hyphen (which is considered a non-word character in AppleScript).
My suggestion would be to split the email content into paragraphs, isolate the lines of text that start with either "Titel" or "Återlämningsdatum", then return those lines, excluding any unwanted words. Here's a handler that does this:
to getFirstParagraphThatStarts on searchString ¬
from theText ¬
apart from excludedStrings : null
local searchString, theText, excludedStrings
repeat with P in theText's paragraphs
if P starts with the searchString then exit repeat
end repeat
set the text item delimiters to {null} & the excludedStrings
text items of P as text
end getFirstParagraphThatStarts
I called the handler much like you did yours:
set theDate to getFirstParagraphThatStarts on subjectText from theContent
set theTitle to getFirstParagraphThatStarts on contentSearch from theContent
(I omitted the apart from parameter to begin with, to test the return result before any exclusions were imposed). The return result was this:
theDate: "Återlämningsdatum: 2017-04-19"
theTitle: "Titel: Ägg : recept & teknik / Tove Nilsson ; [fotografi: Charlie Drevstam]"
Then I added in the apart from parameter:
set theDate ¬
to getFirstParagraphThatStarts on subjectText ¬
from theContent ¬
apart from subjectText
set theTitle ¬
to getFirstParagraphThatStarts on contentSearch ¬
from theContent ¬
apart from contentSearch
and the return results were as follows:
theDate: "2017-04-19"
theTitle: "Ägg : recept & teknik / Tove Nilsson ; [fotografi: Charlie Drevstam]"
which, I think, is more along the lines of what you are wanting.
ADDENDUM 1: Dates
Another thought I had regarding Things 3 is that the due date property might require an actual AppleScript date object, rather than just a string that looks like it might be a date. That is, "2017-04-19" might not be a valid due date. Again, I neither use nor own Things 3, so this is just speculation.
AppleScript date formats are intrinsically tied to your system settings. As I have my system date/time preferences set to use international ISO-8601 date representations, i.e. yyyy-mm-dd, I can create the AppleScript date object straight from theDate variable like so:
date theDate
If this is the case with you, and it turns out that you do require a date object, then you can simply set due date of newToDo to date theDate, or (if using my suggested code from above):
tell application "Things3" to make new to do with properties ¬
{name: theTitle, due date: date theDate}
If, however, your system settings are set differently, you'll need to construct the AppleScript date object yourself. Here's a handler that will do this:
to makeASdate out of {year:y, month:m, day:d}
local y, m, d
tell (the current date) to set ¬
[ASdate, year, its month, day, time] to ¬
[it, y, m, d, 0]
ASdate
end makeASdate
You would then use your theDate variable with this handler like so:
makeASdate out of {year:word 1, month:word 2, day:word 3} of theDate
--> date "Wednesday, 19 April 2017 at 0:00:00"
ADDENDUM 2: Scripting Mail (added 2018-08-12)
After further testing and debugging, you've found that there are other issues with your script that stop it from functioning. This was an oversight on my part, as I ought to have highlighted these errors initially, but became focussed on your handler returning incomplete strings that I neglected to come back to the rest of the script and address its other problems.
The problem section, as you've discerned, is this one here:
using terms from application "Mail"
on perform mail action with messages theMessages for rule theRule
tell application "Mail"
set theContent to content
set theDate to my getFirstWordAfterSearchText(subjectText, theContent)
set theTitle to my getFirstWordAfterSearchText(contentSearch, theContent)
end tell
end perform mail action with messages
end using terms from
You tell application "Mail" to set theContent to content, however, you haven't specified what the content belongs to (or, rather, implicitly, you've specified that the content belongs to application "Mail", which it doesn't).
Presumably, you wish to refer to the content of theMessages that are sent through to this script by your mail rules ?
The other major thing to note is that on perform mail action with messages is an event handler, i.e. it responds to an event taking place in Mail, which causes the handler to be invoked. Defining other handlers elsewhere in the script is perfectly fine; but having stray lines of code that don't belong to any explicit handler will therefore belong to an implicit (hidden) on run handler. This, in a way, is an event handler as well: it responds to a script being run, which is problematic if the script is also being asked to respond to a Mail event simultaneously.
Therefore, the tell app "Things3" block needs to be moved, and I imagine the variable declarations at the beginning need to be housed as well.
Bearing all this in mind, I reworked your script to quite a large extent, whilst also trying to keep it resembling your original so it was somewhat recognisable:
using terms from application "Mail"
on perform mail action with messages theMessages for rule theRule
set dateToken to "Återlämningsdatum: "
set titleToken to "Titel: "
repeat with M in theMessages
set theContent to getMessageContent for M
set datum to restOfLineMatchedAtStart by dateToken from theContent
set titel to restOfLineMatchedAtStart by titleToken from theContent
if false is not in [datum, titel] then
set D to makeASdate out of (datum's words as list)
createToDoWithDueDate on D given name:titel
end if
end repeat
end perform mail action with messages
end using terms from
to makeASdate out of {Y, M, D}
local Y, M, D
tell (the current date) to set ¬
[ASdate, year, its month, day, time] to ¬
[it, Y, M, D, 0]
ASdate
end makeASdate
to createToDoWithDueDate on D as date given name:N as text
tell application "Things3" to make new to do ¬
with properties {name:N, due date:D}
end createToDoWithDueDate
to getMessageContent for msg
local msg
tell application "Mail" to return msg's content
end getMessageContent
on restOfLineMatchedAtStart by searchString from theText
local searchString, theText
ignoring white space
repeat with P in theText's paragraphs
if P starts with the searchString then
set i to (length of searchString) + (offset of searchString in P)
try
return text i thru -1 of P
on error -- matched entire line
return ""
end try
end if
end repeat
end ignoring
false -- no match
end restOfLineMatchedAtStart
Now, as I don't use Mail and cannot test these mail rules out myself, there could be one or two minor tweaks that you'll discover need to be made before it's running properly. On the other hand, it might run correctly as it is, which would amaze me. But, having spent a lot of time thinking about each line of code, I'm hopeful it's close to what you need.
The major change you can see is that every piece of code now belongs inside a handler. The custom handlers each get called from inside the main event handler, on perform mail action with messages. I also changed some of the variable names, mainly as part of my mental debugging that was going on (I changed them several times, and have left them to what their last meaningful label was in my head).
Let me know how it goes. Report back any errors.
NOTE: Don't forget, however, that this script is designed to be called by the Mail application in response to a mail rule being actioned. Running it from within Script Editor will not do anything.

Having trouble with remote folder actions with AppleScript

So I have an Applescript to help with naming photo's appropriately for data entry to assign it to a Unique ID. Since we're using Mac's this is done in AppleScript. This works great but it's only for one machine. What is now needed is to work on multiple machines. What I want to do is put the photos on our server and have the client machines do the action on the folder from there.
The problem I am currently having is that the script does not authenticate the user and does not run the script even though the info is correct. Am I doing this correctly?
tell application "Finder" of machine "eppc://user:password#server.local"
set renameFiles to the selection
set inventoryFiles to every file in folder (((path to documents folder) as text) & "Inventory Photos")
set currentIndex to the count of inventoryFiles
repeat with i from 1 to the count of renameFiles
set currentFile to (item i of renameFiles)
set new_name to ((10000 + currentIndex + i) as text) & ".jpg"
set name of currentFile to "a" & new_name
end repeat
end tell
Thank you for any help

Applescript that filters the subject line of emails in Inbox

I am trying to write a script that does the following job: it goes through all of the emails in the mailbox, finds the ones that have the word "French" in their subject line and then copies all the subject lines of those emails in a text file. Here is what I came up with
tell application "TextEdit"
make new document
end tell
tell application "Mail"
tell the mailbox "Inbox" of account "tigeresque#gmail.com"
set numm to count of messages
repeat with kk from 1 to numm
set wordsub to subject of the message kk
tell application "TextEdit"
if "French" is in wordsub then
set paragraph kk of front document to wordsub & return
end if
end tell
end repeat
end tell
end tell
Unfortunately, I keep receiving the error
"TextEdit got an error: The index of the event is too large to be valid."
and I have already spent a couple of hours trying to fix it without much success. Could you please take a look at my code and see what is wrong with it?
Your main problem is that the number of paragraphs in TextEdit and the number of email messages have nothing to do with each other, so if you're counting on the number of messages then TextEdit will not understand it. For example you may have 50 messages but TextEdit does not have 50 paragraphs so it errors. As such we just use a separate counter for TextEdit.
I made other changes too. I often see errors happen by having one "tell application" block of code inside another... so I separated them. Also notice that the only code inside of any "tell application" block is only what is necessary for that application to handle. This too avoids errors. These are good habits to have when programming.
Therefore give this a try...
set searchWord to "French"
set emailAddress to "tigeresque#gmail.com"
tell application "Mail"
set theSubjects to subject of messages of mailbox "INBOX" of account emailAddress
end tell
set paraCounter to 1
repeat with i from 1 to count of theSubjects
set thisSubject to item i of theSubjects
if thisSubject contains searchWord then
tell application "TextEdit"
set paragraph paraCounter of front document to thisSubject & return
end tell
set paraCounter to paraCounter + 1
end if
end repeat

Resources