Opening the Gmail web interface with keyboard shortcuts

Problem: When I’m not in the browser it takes way too many clicks to get to Gmail, my email client of choice. First activate the browser, then find the Gmail tab (which is often tedious) before finally getting to Gmail. It’s worse if I also want to compose an email. For such a common action this is too complicated.

Solution: I wrote an applescript (triggered with Keyboard Maestro), that directly takes me to the Gmail interface, always in the first tab of the browser. I can also directly go to “compose”, or other often used views in Gmail with a keyboard shortcut.

Here is the applescript:


set theGmailLink to "https://mail.google.com/mail/u/0/#inbox"
tell application "Google Chrome"
  activate
  set allWindows to every window

  repeat with eachWindow in allWindows
    set allTabs to tabs of eachWindow
    set i to 0

    repeat with eachTab in allTabs
      set theURL to get URL of eachTab
      set i to i + 1

      if theURL contains "mail.google.com" then
        #I like to keep my windows this size but it can be commented out
        set the bounds of the eachWindow to {0, 0, 1275, 1800}

        set index of eachWindow to 1
        if theURL is not equal to theGmailLink then
          set URL of eachTab to theGmailLink
        end if

        #focus on the gmail tab
        set (active tab index of eachWindow) to i
        return #exit the script if a gmail tab was open already
      end if
    end repeat
  end repeat

  #this gets executed if no gmail tab was open
  set newTab to make new tab at beginning of tabs of front window
  set URL of newTab to theGmailLink

  #I like to keep my windows this size but it can be commented out
  set the bounds of the front window to {0, 0, 1275, 1800}
end tell

Instead of using a link to the inbox for theGmailLink, you can take any other gmail link from the address bar, like compose ("https://mail.google.com/mail/u/0/#inbox?compose=new"). Now the only thing left is to put it into a simple Keyboard Maestro action:

gmail - open inbox

 

Now getting to Gmail quickly is super easy, just a keyboard shortcut away.

Apptivate – so simple and useful

I love Apptivate. It’s one of those specialized menu bar apps that does few things but does them well. It basically uses keyboard shortcuts to launch applications. What’s so useful to me is that it accepts single key shortcuts. This is one of the reasons why I use an Apple extended keyboard, to have the unassigned keys F13-F19 to launch my most used applications. This makes going through my day so much more pleasant. My setup:

I’m still looking for something for F17, but this covers the apps I use on the mac 90% of the time.

Omnifocus – Search perspective

In Omnifocus I often need to search for items in my database. The search box is restricted to search only within the contexts or projects that are selected in the current perspective. However, I almost always have to search for actions that are not in the current view.

I now use a keyboard shortcut to immediately select my whole library and place the cursor in the search box, so I can start typing my search right a way. It uses Keyboard Maestro and the power of perspectives in Omnifocus.

First, I created a perspective in Omnifocus called “Search” in the projects view with the Library selected and the following focus values to include everything:

Then I created a Keyboard Maestro Macro that only runs within Omnifocus. It calls the perspective and places the cursor in the search bar:

I find it works more reliably with a time delay, but that may have to be adjusted for you.

Monthly Repeating Macros in Keyboard Maestro

I have some scripts that I want to run every month. So far I’ve been using Script Timer for this, but now I want to consolidate all of these repeating tasks in Keyboard Maestro. However, the scheduling trigger options for Keyboard Maestro only seem to enable weekly schedules at most.

I contacted the makers of Keyboard Maestro about this and Peter got back to me really quickly with a great solution: Create a daily repeating trigger but before running the action, just set up a condition that checks for the day of the month. The action will only execute if the condition is satisfied.

If you want to schedule something for the last day of the month, you can use DAY(TIME()+3600*24) = 1.

I’m only beginning to skim the surface of Keyboard Maestro. What a great app.

Calculate 4th Thursday of the month in Omnifocus

I love the power and flexibility of Omnifocus but a common problem I’ve been having is to create monthly repeating actions on the fourth Thursday of the month or other dates like that. I couldn’t find a way to do this within Omnifocus, so I made a script to do it for me.

It relies on a wonderful function from macscripter, which calculates the date of the fourth Thursday from any given day and another one which calculates the first of the month. From those I get the fourth Thursday of the month and create a task in Omnifocus. As with my gmail backup, I log the date the script runs, to make sure there are no duplicate tasks. Now I have a repeating macro in Keyboard Maestro, which runs the script and I never again have to spend time worrying about when to move the car for street cleaning. Here’s the script:

property myContext : "Home"
set todaysDate to (current date)
set thetext to todaysDate as string
set theHomePath to (path to home folder) as string
set thefilePath to theHomePath & "Library:ScriptSupport:LastDate_MoveCar.txt" as string

try
    set oldDate to date (read (file thefilePath))
on error
    set oldDate to (todaysDate - 40 * days)
end try

if month of oldDate is not month of todaysDate then

    set newDate1 to (DateOfThisInstanceOfThisWeekdayBeforeOrAfterThisDate(firstOfThisMonth(todaysDate), Thursday, 4) - 1 * days)
    set newDate2 to DateOfThisInstanceOfThisWeekdayBeforeOrAfterThisDate(firstOfThisMonth(todaysDate), Thursday, 4)
    set newDate3 to (DateOfThisInstanceOfThisWeekdayBeforeOrAfterThisDate(firstOfThisMonth(todaysDate), Friday, 4) - 1 * days)
    set newDate4 to DateOfThisInstanceOfThisWeekdayBeforeOrAfterThisDate(firstOfThisMonth(todaysDate), Friday, 4)

    tell application "OmniFocus"
        tell default document
            set theContext to context myContext
            make new inbox task with properties {name:"move car to opposite side of house", context:theContext, start date:newDate1, due date:newDate2}
            make new inbox task with properties {name:"move car to same side of house", context:theContext, start date:newDate3, due date:newDate4}
            compact
        end tell
    end tell

    try
        set eof of file thefilePath to 0
        write thetext to file thefilePath starting at eof
    on error
        tell application "Finder"
            make new file in (theHomePath & "Library:ScriptSupport") with properties {name:"LastDate_MoveCar.txt"}
        end tell
        write thetext to file thefilePath starting at eof
    end try

end if

on firstOfThisMonth(theDate) -- returns a date
    copy theDate to d
    set d's day to 1
    d -- return the resulting date
end firstOfThisMonth

on DateOfThisInstanceOfThisWeekdayBeforeOrAfterThisDate(d, w, i) -- returns a date
    -- Keep an note of whether the instance value *starts* as zero
    set instanceIsZero to (i is 0)
    -- Increment negative instances to compensate for the following subtraction loop
    if i < 0 and d's weekday is not w then set i to i + 1
    -- Subtract a day at a time until the required weekday is reached
    if d's weekday is w then
        d + (i - 1) * weeks - (d's time)
    else
        repeat until d's weekday is w
            set d to d - days
            -- Increment an original zero instance to 1 if subtracting from Sunday into Saturday 
            if instanceIsZero and d's weekday is Saturday then set i to 1
        end repeat
        -- Add (adjusted instance) * weeks to the date just obtained and zero the time
        d + i * weeks - (d's time)
    end if
end DateOfThisInstanceOfThisWeekdayBeforeOrAfterThisDate

Archiving Gmail

On a recent mac power users episode, Katie and David discussed a few email backup systems. They look really useful, but for my needs they are too sophisticated. I just want to download local copies of my email to my mac on a regular basis. So I thought I’d cook up a script for that.

I use gmail, but am using mail.app as a back end to do a bunch of sorting and, now, backing up. I decided to go with an applescript that is run every few hours by Keyboard maestro. I wanted a script that looks at the “All Mail” folder of gmail and copies all the recent emails to a local mailbox on my mac.

First, I want to keep track of the last time the backup was run, so I create a .txt file to store the date whenever the backup runs, so I only copy the recent emails since then. I also like to file the emails by year on my mac, so put a piece of code in that checks for the year mailbox and creates it, if it doesn’t exist. There are some emails in my “All Mail” that I never read, so to remove that unread label in my folder I mark them all as read in the end.

John Gruber posted a nifty inbox sweeper script that does something similar, but is not applicable to my workflow, since I like to sweep my inbox manually when I process emails to zero. I did get a neat trick from his script though, which copies all selected emails without looping, speeding up the code. I also used mascripter’s great tutorial on accessing files.

To adopt this, you need to create the LastDate_EmailBackup.txt file and put in a date from which you want to start the backup, e.g. in m/d/y format, and adjust the archive mailboxes to your needs.

set todaysDate to current date 
set theText to todaysDate as string 
set theHomePath to (path to home folder) as string 
set thefilePath to theHomePath & "Library:ScriptSupport:LastDate_EmailBackup.txt" as string 
set oldArchiveDate to date (read file thefilePath from 1) 
set newArchiveDate to oldArchiveDate - 1 * hours --allow for some time cushion
set theOldYear to year of oldArchiveDate as string 
set thenewYear to year of newArchiveDate as string 

tell application "Mail" 
set theMailbox to mailbox "All Mail" of mailbox "[Gmail]" of account "Gmail" 

if theOldYear is thenewYear then 
   set selectedMessages to (a reference to (every message of theMailbox whose date received is greater than newArchiveDate)) 
   if mailbox thenewYear of mailbox "Archive" exists then 
      copy selectedMessages to mailbox thenewYear of mailbox "Archive" 
      set read status of every message in mailbox thenewYear of mailbox "Archive" to true 

   else
      make new mailbox with properties {name:"Archive/" & thenewYear}
      copy selectedMessages to mailbox thenewYear of mailbox "Archive" 
      set read status of every message in mailbox thenewYear of mailbox "Archive" to true 
   end if 
else 
   set selectedMessages to every message of theMailbox whose date received is greater than newArchiveDate 
   repeat with eachMessage in selectedMessages 
      set theDate to date received of eachMessage 
      set theYear to year of theDate as string 
      if mailbox theYear of mailbox "Archive" exists then 
         copy eachMessage to mailbox theYear of mailbox "Archive" 
      else make new mailbox with properties {name:"Archive/" & theYear} 
         copy eachMessage to mailbox theYear of mailbox "Archive" 
      end if 
   end repeat 
   set read status of every message in mailbox thenewYear of mailbox "Archive" to true 
   set read status of every message in mailbox theOldYear of mailbox "Archive" to true 
end if 
end tell 
set eof of file thefilePath to 0 
write theText to file thefilePath starting at eof

UPDATE: An earlier version archived mail from up to 2 days before the last archive date. In mail.app it doesn’t seem like this would duplicate a message. However, in the actual Library/Mail folder those messages did get duplicated resulting in a huge number of occupied space. I now archive only email from at most 1 hour before the last archive date to allow for late imports in Gmail from other email addresses. This should keep the number of duplicates to a minimum.

Automator service to create random dropbox link and email it

I use dropbox a lot to share files and a recurring task is to copy a file into the dropbox public folder, create a link and email it off. Additionally, I like using Merlin Mann’s trick of zipping a file, renaming it to something obscure and sending that link. Recently, I found  Brian Hayes’s trick (via Macsparky) to streamline the process using Textexpander.

I wanted to adopt it to my workflow and created an automator service that I can run in the finder, with a file selected. It does the following:

  1. zip the file
  2. rename the zip to something obscure, using Brian’s code
  3. move the zip to the dropbox public folder
  4. create a link to that file, using this code from Chez Moncef, the dropbox forum and stackoverflow
  5. create a new email in mail.app and put the link into the body

To use this service for your needs, you need to edit two values:

You can save the service and access it in the finder by right-clicking on any file. I combine this with a Hazel rule that cleans out my public folder after a certain time. If you use this a lot you could set up a keyboard shortcut for the service in the keyboard preference pane.

Here is what the automator service looks like:

And here is the applescript:

on run {input, parameters}

set str to {}
set end of str to some item of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
repeat 12 times
set end of str to some item of "ABCDEFGHIJKLMNOPQRSTUVWXYZa;rndbcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()_+-=[]>?;:'"
end repeat

set randString to str as string

tell application "Finder"
set theFile to file input
set theNewName to (randString & ".zip")
set the name of file input to theNewName

(*Change the dropbox path if necessary*)
set dest_folder to folder "Documents:Dropbox:Public" of home
move file input to dest_folder
end tell

(*Change YOUR_DROPBOX_ID to your unique dropbox id*)
set the theURL to ("http://dl.dropbox.com/u/YOUR_DROPBOX_ID/" & my text2url(theNewName))
set the clipboard to theURL as text

tell application "Mail"
activate
make new outgoing message with properties {content:the clipboard, visible:true}

end tell
return input
end run

on text2url(thetext)
return do shell script "python -c \"import urllib, sys; print (urllib.quote(sys.argv[1]))\" " & quoted form of thetext
end text2url