Category Archives: Programming for Fun

Monitoring an Outlook Inbox with Python

I get a lot of email at work.  Mostly by robots that are triggering on some event or another.  But after a few years of more and more distribution lists having me on it, the amount of email I get in a day is actually unmanageable.  I’m talking thousands.  The vast majority can be safely ignored, such as “Job ABC completely successfully”.

So I’ve gotten pretty aggressive with Outlook rules in order to filter my incoming email into different folders.  But the rules have always felt limited to me.  For example, I cannot say “If the subject contains this or this, do that“.  I’m limited to a logical and.

Looking into whether or not you can use Python in your Outlook rules, I came across an example where you can actively monitor your Inbox and have access to the messages that come in, all from Python.

C:meDevelopmentpythonoutlook>outlook-monitor.py
Subj: Test Email
Body: Hey!

Test

-Matthew Urch | Programmer Extraordinaire
========

This opens up a whole world of email automation.

The basic script I made that achieves the above is this:

import win32com.client
import pythoncom

class Handler_Class(object):
    def OnNewMailEx(self, receivedItemsIDs):
        for ID in receivedItemsIDs.split(","):
            # Microsoft.Office.Interop.Outlook _MailItem properties:
            # https://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook._mailitem_properties.aspx
            mailItem = outlook.Session.GetItemFromID(ID)
            print "Subj: " + mailItem.Subject
            print "Body: " + mailItem.Body.encode( 'ascii', 'ignore' )
            print "========"
        
outlook = win32com.client.DispatchWithEvents("Outlook.Application", Handler_Class)
pythoncom.PumpMessages()

And then from here, you can do whatever you like with your email messages that are coming in.

Writing a Google Hangouts Bot

I’m a huge fan of programming just for fun.  But often times the fun itself is directly tied to what your program will be doing.  Well yesterday I had a great deal of fun writing a very simple bot for a group Hangout I’m in.

The idea for the bot came about because of a type of battle that was fought over changing the name of the Hangout.

title-battleParticularly from Mr. Jonathan there.  He is a frequent title hijacker.

So what I wanted, was that whenever Jonathan changed the title, it would immediately change to what I have it set to.  If others changed the title, that’s alright by me.  This was a battle between Jonathan and I.

My chosen technology for this bot, was good ol’ jQuery running from a Firebug console.  Since I then wouldn’t have to worry about authentication or any of that.  This bot would be run only when the chat was open.  I wanted it simple.

Right off the bat however, I discovered this would be a little trickier than I had hoped.  The chat windows are running inside iframes.  And those frames are coming from a different place.  So if I attempted to retrieve the document of the iframe, I would trip over the “Same Origin Policy”.  But this is easily worked around if you open the chat in its own window by clicking on the “pop out” button in the middle of the three buttons at the top.

Now I was in business and any Javascript I injected via Firebug would trivially work with the existing elements.  So now I was able to break down the task and code away.

Note: As a precondition to the script of this bot, jQuery needs to be injected.

Opening the Settings and Changing the Title

These parts are fairly trivial Javascript.  To open the settings of the Hangout, we simply want to click the gear at the top:

document.getElementsByClassName( "SU" )[0].click() ;

Which wrapped with a check to ensure we aren’t trying to go to the settings page when we’re already there, looks like this:

var saveButtonSelector = "button:contains('Save'):visible" ; 
function clickCog() {
   if( jQuery( saveButtonSelector ).length == 0 ) {
      document.getElementsByClassName( "SU" )[0].click() ;
   }
}

And to update the title:

document.getElementsByClassName( "editable" )[1].innerHTML = text + "<br />" ;
jQuery( saveButtonSelector )[0].click() ;

When Element Found, Call Function

To update the title, starting from outside the settings page is a little more interesting, since fundamentally we don’t know how long it’s going to take to get there.  So my solution was to monitor for the “Save” button that is on the settings page.  When that button is present and visible, then update the title.

To facilitate this, I needed something event based.  But there is no baked-in event for when an element is found that didn’t previously exist.  And I didn’t want to spend much time looking for a Javascript library that would work for me, so I rolled my own.

We can use the built-in setTimeout method in a loop that polls for the element, and when the element is found, trigger the event.

function whenElementFoundCallFunc( selector, func ) { 
  
  if( jQuery( selector ).length > 0 ) { 
    func() ;
  } else { 

    setTimeout( function() {  
      whenElementFoundCallFunc( selector, func ) ;
    }, 50 ) ; 
    
  }
  
}

Using the above method, and the clickCog() method which checks if you’re already in the settings page, updating the title becomes this:

function setTitleText( text ) { 
  
  clickCog() ; 
  
  whenElementFoundCallFunc( saveButtonSelector, function(){
    document.getElementsByClassName( "editable" )[1].innerHTML = text + "<br />" ;
    jQuery( saveButtonSelector )[0].click() ; 
  } ) ; 
  
}

Which will cause the script to forever poll for the Save button, and when it’s visible, it will set the title to what I want.

Monitoring for Title Changes

The core of this bot is it needs to act on its own when the title is changed.  That means we need something very similar to the above method “whenElementFoundCallFunc”.  We need to check the last title change, see if it was done by Jonathan, and if so, change the title to zyx.

function monitorForChangeByAndCall( person, func ) { 

  var lastTitleChange = jQuery( "span:contains('renamed the Hangout'):last" ).text() ;
  
  if( lastTitleChange.indexOf( person ) == 0 ) { 
    func() ;
  } else { 
    console.info( person + " did not last change title, checking again soon..." ) ; 
  }
    
  setTimeout( function() {  
    monitorForChangeByAndCall( person, func ) ;
  }, 500 ) ;
  
}

Which can be called simply enough:

monitorForChangeByAndCall( "jonathan", function() {
  setTitleText( "Poutine Nation" ) ;
} ) ; 

That’s it!

Surprisingly small, isn’t it?  But it works perfectly and has made it so Mr. Jonathan cannot change the title of the hangout.

bot-title-changingNote: I am not his dad

Entire Script I Run in Firebug

var script=document.createElement('script');
script.src = "https://code.jquery.com/jquery-latest.min.js" ; 
document.getElementsByTagName('head')[0].appendChild(script);

var saveButtonSelector = "button:contains('Save'):visible" ; 

function clickCog() { 
  if( jQuery( saveButtonSelector ).length == 0 ) { 
    document.getElementsByClassName( "SU" )[0].click() ;
  }
}

function setTitleText( text ) { 
  
  clickCog() ; 
  
  whenElementFoundCallFunc( saveButtonSelector, function(){
    document.getElementsByClassName( "editable" )[1].innerHTML = text + "<br />" ;
    jQuery( saveButtonSelector )[0].click() ; 
  } ) ; 
  
}

function whenElementFoundCallFunc( selector, func ) { 
  
  if( jQuery( selector ).length > 0 ) { 
    func() ;
  } else { 

    setTimeout( function() {  
      whenElementFoundCallFunc( selector, func ) ;
    }, 50 ) ; 
    
  }
  
}

function monitorForChangeByAndCall( person, func ) { 

  var lastTitleChange = jQuery( "span:contains('renamed the Hangout'):last" ).text() ;
  
  if( lastTitleChange.indexOf( person ) == 0 ) { 
    func() ;
  } else { 
    console.info( person + " did not last change title, checking again soon..." ) ; 
  }
    
  setTimeout( function() {  
    monitorForChangeByAndCall( person, func ) ;
  }, 500 ) ;
  
}

monitorForChangeByAndCall( "jonathan", function() {
  setTitleText( "Poutine Nation" ) ;
} ) ;