Monday, March 25, 2013

App on Mac through applescript to open GMail web account in Safari

Recently, out of curiosity I started writing an applescript code to make an app to login onto my gmail web account. Once I started it I realized there are many things I needed to consider. Here are the different scenarios:

1) One just booted the system. Hence, the app should start Safari, open Gmail login page and then login with credentials stored in a keychain.

2) One has already logged into his gmail account. But, the page has been closed but account hasn't been logged out. In this scenario, app should not enter the username and password as when it tries to open the Gmail login page your Gmail opens up with your inbox with emails since you already logged in before closing the page.

3) You logged out but didn't close the page. Then, after a while you want to log back in. In this case it should not reopen the login page but find the already logged out page that's open and put the credentials.

4) The account is already opened and active in a tab that is not in focus right now. In this situation, app should simply bring this tab to the front.

5) The other case is where Safari is minimized with account is already open but page is closed, is logged out but page is open, etc.

6) Most important thing is to make sure Gmail login page loads before it starts entering username and password.

7) At the same time accessing username and password has to be secure.

8) Multiple windows and tabs are open and the app should cycle through them to find some of the above mentioned cases.

This code covers all these cases. I'm sure there are many more one might come across. For now this is working great for me on my 3 different macs (office iMac, home iMac and Macbook Pro). All these 3 have mountain lion operating system with Safari 6.0.3.

This code uses apple security tool to access the username and password I have stored in a keychain. This makes it very secure unlike storing the password in the script it self (check for one of my previous scripts).

The one important thing one needs to remember is to change the keychain location in the script based on their username. For example, on all my macs my username is manchu and I store the keychain in the default location of /Users/manchu/Library/Keychain. So, if you save it in the default location then you should just change manchu in the above path with your home directory name (the one that has home/house symbol in the finder window on the left pane).

This code ensures that page has successfully loaded by checking whether File --> Save as becomes active in the Safari menu. I'm not sure whether this is the right way but some how it appears to be working ok. I have given 10 seconds for the page to load. If it doesn't happen before that it will let you know that page has failed to load through announcement as well as display dialog. If you don't want your Mac to announce it then hash the lines with "say".

If you'd like to use this script, then make sure you create a keychain with your Gmail login credentials in it and change the path to it in the script below. You can see my post before this one to know how to create a keychain.

Here is the code:


# Applescript to open Gmail account in Safari.
# Written by Sreedhar Manchu.
# Password retrieval from keychain access has been accomplished with the code
# found on www.vickash.com. The page is titled "More Secure Passwords In Applescript"

set IsDefined to true
set theCredentials to getCredentials of ¬
 "GMailPersonal" from "/Users/manchu/Library/Keychains/WebLogins.keychain"
set userName to account of theCredentials
set thePW to password of theCredentials
set theTitle to "" & userName & "@gmail.com - Gmail" as string
if appIsRunning("Safari") then
 tell application "Safari"
  activate
  if windows is not {} then
   activate
   repeat with j from (count of windows) to 1 by -1
    repeat with i from (count of tabs of window j) to 1 by -1
     set thisTab to tab i of window j
     set thePageName to name of thisTab
     #display dialog thePageName
     if thePageName contains theTitle then
      set current tab of window j to thisTab
      set windowNum to j
      set alreadyLogged to true
     end if
    end repeat
   end repeat
  end if
 end tell
end if
try
 get alreadyLogged
on error
 set IsDefined to false
end try
if IsDefined then
 tell application "Safari"
  activate
  set index of window windowNum to 1
 end tell
else
 set IsDefined to true
 set theTitle to "Gmail: Email from Google" as string
 tell application "Safari"
  activate
  if windows is not {} then
   activate
   repeat with j from (count of windows) to 1 by -1
    repeat with i from (count of tabs of window j) to 1 by -1
     set thisTab to tab i of window j
     set thePageName to name of thisTab
     if thePageName is equal to theTitle then
      #set tabNum to i
      set theWindows to windows
      set winOne to item j of theWindows
      if (miniaturized of winOne) then
       #set miniaturized of winOne to false
       activate
       #delay 0.5
      end if
      #set currentTab to item i of tabs of window j
      set currentTab to item i of tabs of winOne
      #set winOne to item j of windows
      set alreadyOpen to true
     end if
    end repeat
   end repeat
  end if
 end tell
 try
  get alreadyOpen
 on error
  set IsDefined to false
 end try
 if IsDefined then
  tell application "Safari"
   activate
   tell winOne
    set index to 1
    set visible to false
    set visible to true
   end tell
   #set thisTab to tab tabNum of window 1
   #set current tab of window 1 to thisTab
   set current tab of window 1 to currentTab
   tell application "System Events"
    keystroke userName
    keystroke tab
    keystroke thePW
    keystroke return
   end tell
  end tell
 else
  tell application "Safari"
   activate
   if windows is not {} then
    tell window 1
     if (miniaturized) then
      set miniaturized to false
     end if
     set current tab to (make new tab with properties {URL:"https://mail.google.com"})
     #set current tab to (make new tab with properties {URL:"http://www.bsnl.co.in"})
    end tell
   else
    tell application "Safari"
     activate
     make new document with properties {URL:"https://mail.google.com"}
    end tell
   end if
  end tell
  if page_loaded(10) then
   set IsDefined to false
   tell application "Safari"
    activate
    #set theURL to URL of current tab of front window as text
    #set thePageName to name of current tab of front window as string
    set thePageName to name of current tab of window 1 as string
    if thePageName as string is equal to "" or thePageName as string is equal to null or thePageName is equal to missing value then
     set pageIsNotLoading to true
    end if
   end tell
   try
    get pageIsNotLoading
   on error
    set IsDefined to true
   end try
   if IsDefined then
    #display dialog thePageName
    set theTitle to "" & userName & "@gmail.com - Gmail" as string
    if thePageName does not contain theTitle then
     tell application "Safari"
      activate
      tell application "System Events"
       keystroke userName
       keystroke tab
       keystroke thePW
       keystroke return
       say "Gmail login has been successful"
      end tell
     end tell
    end if
   else
    say "Either page has failed to load or Gmail is taking time"
    display dialog "Either page has failed to load or Gmail is taking time!"
   end if
  end if
 end if
end if

on page_loaded(timeout_value) -- in seconds
 tell application "System Events"
  tell process "Safari"
   tell menu 1 of menu bar item "File" of menu bar 1
    repeat with i from 1 to timeout_value
     if enabled of menu item "Save as..." is false then
      delay 0.5
     else
      return true
     end if
     if i is timeout_value then
      #set timeoutValue to timeout_value * 0.5
      beep
      display dialog "" & timeout_value * 0.5 & " seconds have elapsed; Either page has failed to load or Gmail is taking time!"
      return false
     end if
    end repeat
   end tell
  end tell
 end tell
end page_loaded

on extractData(theText, theFieldName, theEndDelimiter, spaces)
 set theDataStart to the offset of theFieldName in theText
 if theDataStart = 0 then
  return ""
 else
  set theDataStart to theDataStart + (length of theFieldName) + spaces
  set theData to text theDataStart through end of theText
  set theDataEnd to ((offset of theEndDelimiter in theData) - 1)
  set theData to text 1 through theDataEnd of theData
 end if
end extractData

on getCredentials of theKeychainItem from theKeychain
 set theKeychainPath to (POSIX path of theKeychain) as text
 try
  set theKeychainAlias to (POSIX file theKeychainPath) as alias
  set theKeychainPath to (POSIX path of theKeychainAlias)
 on error
  return "Keychain file not found at specified location: " & ¬
   theKeychain as text
 end try
 try
  set theResult to do shell script ¬
   "security 2>&1 find-generic-password -gs " & ¬
   theKeychainItem & " " & ¬
   quoted form of theKeychainPath
  set theAccount to extractData(theResult, "\"acct\"<blob>=\"", "\"", 0)
  set thePassword to extractData(theResult, "password: \"", "\"", 0)
  return {account:theAccount, password:thePassword}
 on error
  return "Generic password item specifeid does not exist in keychain: " & ¬
   theKeychainItem
 end try
end getCredentials

on appIsRunning(appName)
 tell application "System Events" to (name of processes) contains appName
end appIsRunning

No comments:

PBS Script Generator: Interdependent dropdown/select menus in Javascript

PBS SCRIPT GENERATOR
SH/BASH TCSH/CSH
Begin End Abort

About Me

LA, CA, United States
Here I write about the battles that have been going on in my mind. It's pretty much a scribble.

Sreedhar Manchu

Sreedhar Manchu
Higher Education: Not a simple life anymore