Monthly Archives: March 2015

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" ) ;
} ) ; 

Windows: Change Yourself From Admin to a Standard User

A practice that can help maintain security on your Windows machine is to not run as an Admin all the time.  It’s far more secure to run as a Standard User and when you need Admin privileges, you enter the credentials of a different user that is an Admin.

But what if you’ve already been running as an Admin for a while and have your account set up just how you like?

This was my thought process whenever I heard the advice of running as a Standard User instead of an Admin.

Next time I set up a machine for myself, I’ll set my main account as a Standard User…

But it turns out that was entirely misplaced hesitation because switching down to a standard user couldn’t be easier.

Simply create yourself an extra Admin account with a good password, and then in the Users section of the Control Panel, change the level of your own account down to Standard.  Done!