I developed an applescript to sort incoming mail message attachments into into student folders.
Quick hints:
1) Apple hids the User's library folder. You will want to make it visible.
http://osxdaily.com/2013/10/28/show-user-library-folder-os-x-mavericks/
2) Place this Applescript in this folder with name ShortMailDownload.scpt. You may not use the App extension.
"/Users/mac/Library/Application Scripts/com.apple.mail"
3) Mail > Preferences... > Rules > Add Rule
"Perform the following actions" [ buttons should be ]
"Run AppleScript" "ShortMailDownload"
(*
Requirements:
https://discussions.apple.com/thread/8480829
Quick hints
1) Apple hids the User's library folder. You will want to make it visible.
http://osxdaily.com/2013/10/28/show-user-library-folder-os-x-mavericks/
2) Place this Applescript in this folder with name ShortMailDownload.scpt. You may not use the App extension.
"/Users/mac/Library/Application Scripts/com.apple.mail"
2) The first "on" statement needs to be
on perform mail action with messages theMessages
3) Mail > Preferences... > Rules > Add Rule
"Perform the following actions" [ buttons should be ]
"Run AppleScript" "ShortMailDownload"
4) When saving you may get a warning message that some other app, Mail, has made changes to the script. When ever the script is run, the script changes a global variable which modifies the script. Ignore and save any way.
Limitation: Yosemite, 10.10.5, Mail Version 8.2 (2104) only supports extracting one attachment. No problem in High Sierra, 10.13.6.
off topic. Clicking on menu item:
tell application "System Events" to click menu item "Date" of menu "Sort By" of menu item "Sort By" of menu "View" of menu bar item "View" of menu bar 1 of process "Mail"
Based on the example " Sample Rule Action Script.scpt" script in macOS 10.6.8 found in folder
"/Library/Scripts /Library/Scripts/Mail Scripts/Rule Actions"
Sample Rule Action Script.scpt
I think "on perform mail action" needs to be the first "on" routine.
-- If run as an ordinary script, instead of directly from the Scripts
-- menu, it will call the default handler instead.
on r u n
tell application "Mail" to set selectedMessages to selection
tell me to perform mail action with messages (selectedMessages)
end r u n
http://hints.macworld.com/article.php?story=20070215145127300
There is lots of outdated advice on download attachments found by Google.
https://stackoverflow.com/questions/39882312/how-to-download-an-attachment-from-mail-via-applescript-and-mail-rules
Copyright 2018 rccharles
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*)
(* Uncomment when you want to debug this script in the Script Editor. Go into Mail and select a message. Run this script. *)
(*
on run
tell application "Mail"
try
set theSelectedMessages to selection
log "class of theSelectedMessages is " & class of theSelectedMessages
log "count of theSelectedMessages is " & (count of theSelectedMessages)
on error errMsg number n
display dialog "Silly, you need to select a message in Mail." & return & "on error the errMsg is " & errMsg & " number is " & n giving up after 8
return
end try
-- not used, don't know how to pass to perform ...
set theRule to {name:"dummyRule"}
tell me to perform mail action with messages theSelectedMessages -- for theRule
end tell -- app "Mail"
end run
*)
(**)
using terms from application "Mail"
on perform mail action with messages theMessages --for rule theRule
-- Suggest this code be the first "on" in file, no on r u n & no assumed on r u n either
-- Required by debug routine.
global debugRunning
set debugRunning to ""
-- debug status for displaying messages in this routine.
set debugSwitch to true
log "debugSwitch is " & debugSwitch
-- initialize
set longWait to 20
(*
Where to place files
*)
-- intermediate folder in Home folder
-- must be Downloads for later version of macOS
set intermediateDownloadFolder to "Downloads:"
-- Folder inside of Downloads folder
set intermediateSubFolder to "subFolderForMailDownload"
-- final destination folder in Home folder
set finalDestination to "Students"
set startMsg to "perform mail with " & getMyName() & " ====== " & ((current date) as string) & " ====== perform mail "
my debugLog(startMsg)
--display dialog startMsg giving up after 2
set intermediateFolder to intermediateDownloadFolder & intermediateSubFolder
log "intermediateFolder is " & intermediateFolder
if (count of theMessages) = 0 then
log "odd no messages to process."
display dialog "Odd, no messages to process." giving up after longWait
return 1
end if
-- mail requires download to the Downloads or subfolders of Downloads
-- for some reason, this gets the path to this app when invoked by mail.
set pathToHome to (path to home folder) as string
log ("pathToHome is " & pathToHome)
set listOfPathToHome to textToList(pathToHome, ":")
set pathToHome to item 1 of listOfPathToHome & ":" & item 2 of listOfPathToHome & ":" & item 3 of listOfPathToHome & ":"
log ("revised pathToHome is " & pathToHome)
-- make intermediate folder
try
tell application "Finder"
set newfolder to make new folder at (pathToHome & intermediateDownloadFolder) with properties {name:intermediateSubFolder}
my debugLog("Created folder " & pathToHome & intermediateDownloadFolder & intermediateSubFolder)
end tell
on error errMsg number n
-- It's ok if the folder already exits [ -48 ]. Put out warning for all other errors.
if n is not -48 then
set commonError to "on error " & intermediateSubFolder & " the errMsg is " & errMsg & " number is " & n
display dialog commonError giving up after longWait
my debugLog(commonError)
return 2
end if
end try
-- make final destination folder
try
tell application "Finder"
set newfolder to make new folder at pathToHome with properties {name:finalDestination}
my debugLog("Created final destination folder " & pathToHome & finalDestination)
end tell
on error errMsg number n
-- It's ok if the folder already exits [ -48 ]. Put out warning for all other errors.
if n is not -48 then
set commonError to "on error " & finalDestination & " errMsg is " & errMsg & " number is " & n
display dialog commonError giving up after longWait
my debugLog(commonError)
return 3
end if
end try
tell application "Mail"
--activate
log "was activated"
-- look throught the messages we selected.
repeat with aMessage in theMessages -- loop through each message
log "class of aMessage is " & class of aMessage
-- get the senders email address
set fromMail to aMessage's sender
my debugLog("fromMail is " & fromMail)
-- Create folder for the individual student.
-- ---- Set final target folder ---->
set pathToStudents to pathToHome & finalDestination & ":"
my debugLog("pathToStudents is " & pathToStudents)
repeat 1 times -- sinulate continue
try
tell application "Finder"
set newStudent to make new folder at pathToStudents with properties {name:fromMail}
my debugLog("Created final destination subfolder " & pathToStudents & fromMail)
end tell
on error errMsg number n
-- It's ok if the folder already exits [-48 ]. Put out warning for all other errors.
if n is not -48 then
set commonError to "attempting to create " & pathToStudents & fromMail & return & " on error the errMsg is " & errMsg & " number is " & n
display dialog commonError giving up after longWait
my debugLog(commonError)
exit repeat -- look at next message. simulate iterate here.
end if
end try
set pathToTheStudent to pathToStudents & fromMail & ":"
my debugLog("pathToTheStudent is " & pathToTheStudent)
-- repeat with aFile in aMessage's mail attachments
-- set ourList to every mail attachment of aMessage
repeat with aFile in (mail attachments of aMessage)
log "save aFile"
repeat 1 times -- sinulate continue
--if (downloaded of aFile) then -- check if file is already downloaded
set attachmentName to name of aFile
my debugLog("attachmentName is " & attachmentName)
set {fileName, fileExt} to my getNameExt(attachmentName)
log "fileName is " & fileName & " fileExt is " & fileExt
-- ---- Intermediate folder target ---->
-- apple changed mail to require the download to be in the downloads folder :-(
set pathForDownload to pathToHome & intermediateFolder & ":"
set clearCount to 0
set destPath to pathForDownload & attachmentName
set finalDestPath to pathToStudents & fromMail & ":" & attachmentName
log "check for free name. clearCount is " & clearCount & return & " destPath is " & destPath & return & " finalDestPath is " & finalDestPath
try
repeat while my fileExists(destPath) or my fileExists(finalDestPath)
if clearCount > 300 then
set commonError to "could not find free name when searching for free name. clearCount is " & clearCount & " clean up intermediate and final foldrs. " & return & destPath & return & finalDestPath
my debugLog(commonError)
-- have to throw an error to get out of this repeat. Could have set a switch I guess.
display dialog commonError giving up after longWait
error "cannot find free file name" number 8110
end if
set clearCount to clearCount + 1
log "clearCount is " & clearCount
set destPath to pathForDownload & fileName & "#" & clearCount & "." & fileExt
set finalDestPath to pathToStudents & fromMail & ":" & fileName & "#" & clearCount & "." & fileExt
my debugLog("searching for free name. clearCount is " & clearCount & return & " destPath is " & destPath & return & " finalDestPath is " & finalDestPath)
end repeat -- while
on error errMsg number n
if n is not 8110 then
set commonError to "attempting to create free filename " & return & " on error the errMsg is " & errMsg & " number is " & n
display dialog commonError giving up after longWait
my debugLog(commonError)
end if
exit repeat -- look at next message. simulate iterate here.
end try
my debugLog("found usable name. destPath is " & destPath)
save aFile in destPath as native format
log "file was saved."
-- move it right on to file location.
-- debuggin to see if we got the right name
my debugLog("Saved to intermediate. destPath is " & destPath)
if my fileExists(destPath) then
tell application "Finder"
try
-- from file filename path to full folder name path :-(.
move destPath to pathToTheStudent
my debugLog("saved to final folder pathToTheStudent is " & pathToTheStudent)
on error msg number n
set outMsg to "Got error message while moving " & destPath & " to " & pathToTheStudent & return & "message is " & msg & return & "number is " & n
my debugLog(outMsg)
display dialog outMsg giving up after longWait
-- might as well try the next attachment
exit repeat -- look at next message. simulate iterate here.
end try
end tell
else
-- very bad, folder doesn't exist as expected.
my debugLog("very bad, folder " & destPath & " doesn't exist as expected.")
exit repeat -- look at next message. simulate iterate here.
end if
end repeat -- one time to make like a continue statment
end repeat -- next attached file
end repeat -- continue
end repeat -- next message
end tell --tell app "Mail"
return 0
end perform mail action with messages
end using terms from
(* ======================== Subroutines ======================= *)
-- ------------------------------------------------------
(*
*)
on appendToFile(fileId, theData)
local theSize, writeWhere
set theSize to (get eof fileId)
set writeWhere to theSize + 1 as integer
write theData to fileId starting at writeWhere
end appendToFile
-- ------------------------------------------------------
(*
debug(<string>)
Write messages to a log file.
-- Need to place these two lines in the calling routine.
global debugRunning
set debugRunning to ""
-- references appendToFile()
-- example:
debug("start program. Reading from " & listOfFiles)
found here: /Users/mac/Documents/BJ\ Prior\ Years/BJ2004/sendmailapp2\ copy
*)
on debug(theMessage)
-- return
global debugRunning
local theSize, startupDiskName, pathToLog, fileReference
set pathToLog to (path to home folder as text) & "tryAttachmentsLog.txt"
-- log "pathToLog is " & pathToLog
-- display dialog "pathToLog is " & pathToLog giving up after 4
try
-- Complete the path.
set pathToLog to pathToLog as text
set fileReference to (open for access file pathToLog ¬
with write permission)
if debugRunning = "" then
set theSize to (get eof fileReference)
if theSize > 0 then
appendToFile(fileReference, " " & return)
end if
appendToFile(fileReference, " --- debug on " & ((current date) as string) & " --- " & return)
set debugRunning to "running"
end if
-- log "theMessage " & theMessage
-- display dialog "in debug..." & return & "theMessage " & theMessage giving up after 3
appendToFile(fileReference, theMessage & return)
close access fileReference
tell application "Finder"
set the creator type of the file pathToLog ¬
to "R*ch"
end tell
on error mes number n
try
set commonErr to "error ... " & mes & " error number is " & n
log commonErr
close access fileReference
display dialog commonErr giving up after 4
end try
end try
-- log "end of debug"
end debug
(*
write log message to script editor log and to our file log
*)
on debugLog(theMessage)
log "debugLog: " & theMessage
return debug(theMessage)
end debugLog
-- ------------------------------------------------------
(*
ideas from:
https://stackoverflow.com/questions/3469389/applescript-testing-for-file-existence
use the alias way.
*)
on fileExists(theFile) -- (String) as Boolean
(* "System Events" and "Finder" checking for file existance revealed problems. l*)
set debugging to false
if debugging then log " fileExists: theFile is " & theFile
try
set theAlias to theFile as alias
set theExistance to true
on error errMsg number n
if debugging then log " fileExists: n is " & n
-- File or folder doesn't exist.
if n is not -43 then
set commonError to "on error the errMsg is " & errMsg & " number is " & n
if debugging then log " fileExists: " & commonError
display dialog commonError giving up after 10
-- cause grief above.
error "Failure of alias." number -1
else
set theExistance to false
end if
end try
if debugging then log " fileExists: theExistance is " & theExistance
return theExistance
end fileExists
(*
Philip Regan
https://stackoverflow.com/questions/3469389/applescript-testing-for-file-existence
*)
(*on fileExists(theFile) -- (String) as Boolean
tell application "System Events"
if exists file theFile then
return true
else
return false
end if
end tell
end fileExists*)
-- ------------------------------------------------------
(*
Yvan Koenig
https://macscripter.net/viewtopic.php?id=43133
with mods for no extension present
*)
on getExt(theName)
if (offset of "." in theName) is greater than 0 then
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {"."}
set theExt to last text item of theName
set AppleScript's text item delimiters to saveTID
if theExt ends with ":" then set theExt to text 1 thru -2 of theExt
else
set theExt to ""
end if
return theExt
end getExt
-- ------------------------------------------------------
(*
Input: a file with or without an extension.
hhas
https://forums.macrumors.com/threads/applescript-to-get-file-name.927338/
may not work with folders with extensions like apps.
Test cases:
set fileName to " "
set fileExt to my getExt(attachmentName)
log "fileName is " & fileName & " fileExt is " & fileExt
set fileExt to my getExt("testfileNo")
log "fileName is " & fileName & " fileExt is " & fileExt
set fileExt to my getExt("path:to:testfileNo:")
log "fileName is " & fileName & " fileExt is " & fileExt
log ">>>>>>>>>>>>>>>>>>>>>>"
set {fileName, fileExt} to my getNameExt(attachmentName)
log "fileName is " & fileName & " fileExt is " & fileExt
set {fileName, fileExt} to my getNameExt("testfileNo")
log "fileName is " & fileName & " fileExt is " & fileExt
set {fileName, fileExt} to my getNameExt("path:to:testfileNo:")
log "fileName is " & fileName & " fileExt is " & fileExt
*)
on getNameExt(fileName)
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to "."
if fileName contains "." then
set {displayName, nameExt} to {text 1 thru text item -2, text item -1} of fileName
else
set {displayName, nameExt} to {fileName, ""}
end if
set AppleScript's text item delimiters to saveTID
return {displayName, nameExt}
end getNameExt
-- ------------------------------------------------------
(*
modified to let the extension be.
by mklement0
https://stackoverflow.com/questions/5770384/how-find-the-file-name-of-an-executing-applescript
*)
on getMyName()
local myAlias, myName
tell application "System Events"
set myAlias to path to me -- alias to the file/bundle of the running script
set myName to name of myAlias -- filename with extension, if any.
-- leave extension alone.
end tell
return myName
end getMyName
-- ------------------------------------------------------
(*
ls_l is list file with options
the format is best for debuging.
example usage:
set {fileExists, fromUnix} to ls_l(attachmentNamePath, "-l")
log "attachmentNamePath fileExists is " & fileExists & return & " fromUnix is " & fromUnix
*)
on ls_l(attachmentNamePath, options)
--log "ls_l"
--log options
set unixAttachmentNamePath to POSIX path of attachmentNamePath
--log "unixDesktopPath = " & unixAttachmentNamePath
set quotedUnixAttachmentNamePath to quoted form of unixAttachmentNamePath
--log "quoted form is " & quotedUnixAttachmentNamePath
try
set fromUnix to do shell script "ls " & options & " " & quotedUnixAttachmentNamePath
set fromUnix to "ls " & options & return & fromUnix
set fileExists to true
on error errMsg number n
set fromUnix to "ls " & options & " error..." & errMsg & " with number " & n
set fileExists to false
end try
return {fileExists, fromUnix}
end ls_l
-- ------------------------------------------------------
(*
textToList seems to be what you are trying to do
thisText is the input string
delim is what to split on
returns a list of strings.
- textToList was found here:
- http://macscripter.net/viewtopic.php?id=15423
*)
on textToList(thisText, delim)
set resultList to {}
set {tid, my text item delimiters} to {my text item delimiters, delim}
try
set resultList to every text item of thisText
set my text item delimiters to tid
on error
set my text item delimiters to tid
end try
return resultList
end textToList