Friday, April 27, 2012

Updates to Google Voice scripts

Google changed the way contacts are downloaded, which broke the mass contact feature of a couple of the scripts. I was able to spend a few minutes this morning taking care of those issues, so everything should be back the way it was before.

You can find the latest version of the Google Voice scripts on my github page here:

https://github.com/hillmanov/gvoice

Enjoy!

Saturday, October 8, 2011

Login problems solved!

Thanks to another reader (known only to me by 'First Last') the login issues have been resolved!


It turns out that the problem was much more simple than I originally thought. 'First Last' pointed out that the query string parameter 'service' needed to be passed along with the value of 'grandcentral' (which is the name of the company that Google acquired in order to bring us Google Voice). 


The change has been made, and the latest versions of the various Google Voices script can be downloaded from my github repo.


From now on, my scripts will be available at that location. It is much easier for me to maintain, and for users of github is it much easier for you to keep up with the latest version and contribute back if you wish (which would be nice!).

Saturday, October 1, 2011

Login problems

Some of you may have already noticed, but the gvoice scripts I wrote no longer appear to be working. The script will either crash or report that it cannot login.

I am aware of this issue and have looked into it. Google changed the login screen again, and despite my efforts over the last few days I am unable to find out how to authenticate programmatically as I was before.

If any of you have figured out how to do this, I would love to know!

To the rest of you: hang in there. I know that this script has been very important and useful for many people. Once I figure out how to get around this I will put an update up here and in the git repo.

Wednesday, January 5, 2011

gvoice.py - Updated!

After too long of a wait...here is an update!

Big thanks to Mike who helped me by showing me where to get the newly required "tok" variable when downloading the contact list.

I haven't been able to setup py2exe on my current computer yet, so I do not have a standalone .exe version yet (any volunteers?) but the following files are now up to date:

- gvoice.py
- gvAllInOne.py
- gvMassContact.py

Get them all by downloading this Zip file

Happy texting!

Saturday, January 23, 2010

Python - Easy jQuery lightBox Gallery

If you ever have to put images up on the internet, I recommend using the excellent jQuery plugin lightBox.

Not long ago, a relative asked me to help her with her website. It was made by the previous webmaster using Microsoft Publisher, and was a pain to maintain. Updating the site was especially cumbersome when she needed several images put up in a gallery. It took a long time to resize all of the images and put them in a table - normally around an hour or more. I rewrote the site in PHP and created a neat tool to automatically create a lightBox gallery for me - all I needed to do was drag and drop the folder with the original images in it onto the script file and I had my gallery ready to go! I thought I should share the script for those who may find it useful.

Fist off, however, you will need to install the Python Image Library (PIL). This handles the resizing part of the script.
You will also need jQuery included on your page, as well as everything needed for lightBox.

Once you have that set up, you should be able to use my script: (I don't like the "all catching" except, but it was useful for debugging)

import os
from PIL import Image
import sys
import glob

try:
base_dir = '<insert the full path to the root directory of your site (eg. c:\\sites\\myawesomesite\\)>'
img_dir = base_dir + '<insert the folder where you keep your images (based off the base_dir defined above (eg. \\images\\)>'
thumbs_dir = img_dir + '<insert the folder where you want the thumbnails placed, relative to the images dir defined above (eg. thumbs\\)>'
originals_dir = img_dir + '<insert the folder where you want the originals placed, relative to the images dir defined above (eg. originals\\)>'
web_base_path = '<put the relative URL path to the images directory (eg. /images/)>'
web_thumbs_path = web_base_path + '<put the relative URL path to the thumbs directory (relative to the images dir defined above) (eg. thumbs/)>'

temp_gallery_file = base_dir + '<put the name of the file where you want the gallery code to be stored (eg. TEMP_GALLERY_FILE.html)>'

column_amount = 3 # Put how many columns wide you want the gallery to be

img_display = """\t\t<td>
<div class="image">
<a href="{0}" title="">
<img src="{1}" alt="" />
</a>
<span class="description"></span>
</div>
<br />
</td>
"""

if len(sys.argv) == 1:
sys.exit(1)

resource = sys.argv[1]

# Default width, if it is less, than the old width is used.
thumb_width = 200
max_orignal_width = 700

# Keep the aspect ration (based on the width of the image)
def aspectRatioSize(desired_width, original_size):
original_width = original_size[0] + .0
original_height = original_size[1] + .0
return (desired_width, desired_width * (original_height / original_width))

def getNextAvailableName(folder, img_name, ext):
index = 1
while os.path.exists(folder + img_name + '_' + str(index) + ext):
index += 1
return img_name + '_' + str(index)+ ext

gallery_file = open(temp_gallery_file, "w")

gallery_file.write("<div class=\"gallery\">\n")
gallery_file.write("<table>\n")
gallery_file.write("\t<tr>\n")

base_image_name = os.path.basename(resource)
images = [image for image in glob.glob(resource + "/*.*")]
# Loop through all images in directory, make thumbs and resize the original
for index, image in enumerate(images):
index += 1
print image
img_name, ext = os.path.splitext(os.path.basename(image))
im = Image.open(image)
img_size = im.size
thumb_name = getNextAvailableName(thumbs_dir, base_image_name, ext)
modified_large_name = getNextAvailableName(img_dir, base_image_name , ext)
original_name = getNextAvailableName(originals_dir, base_image_name, ext)

# Thumbnail
thumb = im.resize(aspectRatioSize(thumb_width, img_size), Image.ANTIALIAS)
thumb.save(thumbs_dir + thumb_name)

# Modified Large image
modified_large = im.resize(aspectRatioSize(max_orignal_width, img_size), Image.ANTIALIAS)
modified_large.save(img_dir + modified_large_name)

# Original
im.save(originals_dir + original_name)

if index != 1 and index % column_amount == 1:
gallery_file.write("\t<tr>\n")

gallery_file.write(img_display.format(web_base_path + modified_large_name, web_thumbs_path + thumb_name))

if not index % column_amount:
gallery_file.write("\t</tr>\n")

if index % column_amount > 0:
gallery_file.write("\t</tr>\n")

gallery_file.write("</table>\n")
gallery_file.write("</div>")


gallery_file.close()
except Exception as e:
print e
raw_input()



I also use the following CSS to make the gallery, once created, look the way I want.


.gallery .image {
margin: 0 auto;
text-align: center;
vertical-align: top;
}

.gallery .image .description {
font-size: 11px;
color: #000066;
text-align: center;
display: block;
line-height: normal !important;
}

.gallery td {
vertical-align: top;
}

.gallery table {
margin: 0 auto;
width: 90%
}

.gallery img {
border: none;
}


Once you have all your paths defined correctly, you can use the script by simply dragging the folder with the pictures you want in your gallery onto the script. Keep in mind, that in the current script, it will rename the pictures based on the name of the folder they are in (folder_name_1, folder_name_2, folder_name_3...). I needed something like this to avoid name collisions (it checks to make sure the name is unique).

Once the script finishes, the HTML that the script creates will be in the TEMP_GALLERY_FILE.html file (or whatever you named it). Copy and paste that into the site you want the gallery to be, and you are good to go.

I will be posting a demonstration video shortly.

Saturday, December 26, 2009

Python - Google App Engine + Boggle Solver + Web2Py

Click here to go right to the solver

Lately I have been experimenting with a Python web development framework called Web2Py, and so far I have been very impressed. As with any language/platform/framework, there are those who love it and those who hate it. I happen to be in the group that loves Web2Py. I also happen to like Django, another framework from which Web2Py received a lot of inspiration.

One thing that I really like about Web2Py is that it is extremely easy to get running - no installation is needed whatsoever. Download, start up the built-in server and get to developing. No configuration is needed either (You can visit their Home Page to find out more).

More importantly though is that it runs on the Google App Engine without any modification whatsoever. That is one of the things that made me choose it over Django when deciding which framework to use to put up my latest side project. Django does run on the Google App Engine - lots of people have done it, but there are too many complicated steps to get things going. Not the case at all with Web2Py.

Several posts back I posted my Boggle-Board solving code. It runs from the command line, and is not too user friendly. I decided to wrap a nice GUI around it and put it online. The resulting Boggle Solver can be found here.

Things to note about the solver:

  • I wrote this app because I was unhappy with all the other online boggle solvers out there

  • For the random boards, I use actual Boggle dice configurations not just random letters

  • Written completey in Python using the Web2Py framework, running on the Google App Engine

  • Uses a dictionary of 170+ words (I am pretty sure it is the Enable2k dictionary)

  • Visit my blog to see how the words are found



Enjoy!

PS: If you are a CS student somewhere and you have this same assignment: Do your own work.

Friday, October 9, 2009

Python - Custom Google Voice API - Installable Module

Google Voice is a great service, but it lacks a couple of important things:
  1. An official API from Google
  2. A way to send mass SMS messages
I have spend some time creating my own unofficial API that is, I hope, easy to use and extend. I am by no means a seasoned Python developer, but I know enough to get what I want done. Many of the posts on my blog that interact with Google Voice depend on a "gvoice.py" script that I wrote, which contains several helpful classes. These class allow you to perform some basic actions, such as:

  1. Logging in to your Google Voice Account
  2. Sending a text message
  3. Placing a phone call

The script also allows you to:
  1. Download your Google Contacts into memory (Used mainly to get contact information to call or send a message)
  2. Programmatically select from you Contact Groups which people to contact
  3. Retrieve the numbers you have set up to work with your Google Voice account

Currently the script has only been tested using Python 2.6 (Python 3 might work, but I am not sure).

You can download the module with installation instructions below. Or, just scroll down to the bottom of the post to see the source in it's entirety.


Here are some of the posts where I show examples on how to use this module:

And the complete source:


"""
gvoice.py

Created by: Scott Hillman

http://www.everydayscripting.blogspot.com

This module comes as is an with no warranty.
You are free to use, modify and distribute this
code however you wish, but I ask that if you post
it anywhere, you at least make reference to me and
my blog where you acquired it.
"""

import csv
import sys
import re
import urllib
import urllib2

class GoogleVoiceLogin:
"""
Class that attempts to log in the Google Voice using the provided
credentials.

If either no password or email is provided, the user will be
prompted for them.

Once instantiated, you can check to see the status of the log in
request by accessing the "logged_in" attribute

The primary usage of a GoogleVoiceLogin object is to be passed
in to other constructors, such as the TextSender, or NumberDialer
"""

def __init__(self, email = None, password = None):
"""
Given the email and password values, this method will attempt to log
in to Google Voice. The "response" attribute can be checked to
see if the login was a success or not.

If the login was successful, the "opener" and "key" attributes will
be available to use when creating other objects.

To use an this object with the other classes in this module, simply
pass it in to the constructor. (ie text_sender = TextSender(gv_login))
"""

if email is None:
email = raw_input("Please enter your Google Account username: ")
if password is None:
import getpass
password = getpass.getpass("Please enter your Google Account password: ")

# Set up our opener
self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
urllib2.install_opener(self.opener)

# Define URLs
self.login_page_url = 'https://www.google.com/accounts/ServiceLogin'
self.authenticate_url = 'https://www.google.com/accounts/ServiceLoginAuth'
self.gv_home_page_url = 'https://www.google.com/voice/#inbox'

# Load sign in page
login_page_contents = self.opener.open(self.login_page_url).read()

# Find GALX value
galx_match_obj = re.search(r'name="GALX"\s*value="([^"]+)"', login_page_contents, re.IGNORECASE)

galx_value = galx_match_obj.group(1) if galx_match_obj.group(1) is not None else ''

# Set up login credentials
login_params = urllib.urlencode({
'Email' : email,
'Passwd' : password,
'continue' : 'https://www.google.com/voice/account/signin',
'GALX': galx_value
})

# Login
self.opener.open(self.authenticate_url, login_params)

# Open GV home page
gv_home_page_contents = self.opener.open(self.gv_home_page_url).read()

# Fine _rnr_se value
key = re.search('name="_rnr_se".*?value="(.*?)"', gv_home_page_contents)

if not key:
self.logged_in = False
else:
self.logged_in = True
self.key = key.group(1)

class ContactLoader():
"""
This class is used to download and organize a csv file
of Google Contacts.It is often used in conjunction with
the ContactSelector class.

Example:

contact_loader = ContactLoader(gv_login)
contact_selector = ContactSelector(contact_loader)
"""
def __init__(self, gv_login):
"""
Pass in a GoogleVoiceLogin object, and the persons Google Contacts
Will be downloaded and organized into a structure called
"contacts_by_group_list"
which is organized in form:

[(1, ('group_name', [contact_list])), (2, ('group_name', [contact_list]))]

Which allows for easy access to any group.
"""
self.opener = gv_login.opener
self.contacts_csv_url = "http://mail.google.com/mail/contacts/data/export"
self.contacts_csv_url += "?groupToExport=^Mine&exportType=ALL&out=OUTLOOK_CSV"

# Load ALL Google Contacts into csv dictionary
self.contacts = csv.DictReader(self.opener.open(self.contacts_csv_url))

# Create dictionary to store contacts and groups in an easier format
self.contact_group = {}
# Assigned each person to a group that we can get at later
for row in self.contacts:
if row['First Name'] != '':
for category in row['Categories'].split(';'):
if category == '':
category = 'Ungrouped'
if category not in self.contact_group:
self.contact_group[category] = [Contact(row)]
else:
self.contact_group[category].append(Contact(row))

# Load contacts into a list of tuples...
# [(1, ('group_name', [contact_list])), (2, ('group_name', [contact_list]))]
self.contacts_by_group_list = [(id + 1, group_contact_item)
for id, group_contact_item in enumerate(self.contact_group.items())]

class Contact():
"""
Simple class to contain information on each Google Contact person.

Only stores information on:
First Name
Last Name
Mobile Number
Email address
"""
def __init__(self, contact_detail):
"""
Extract data from the given contact_detail

The following attributes are available:

first_name
last_name
mobile
email
"""
self.first_name = contact_detail['First Name'].strip()
self.last_name = contact_detail['Last Name'].strip()
self.mobile = contact_detail['Mobile Phone'].strip()
self.email = contact_detail['E-mail Address'].strip()

def __str__(self):
return self.first_name + ' ' + self.last_name

# Class to assist in selected contacts by groups
class ContactSelector():
"""
Class with helps select contacts after using the ContactLoader
object to download them.

Provides methods to:
1) Display the list of groups (get_group_list())
2) Set the selected group to work with (set_selected_group(group_id))
3) Get the contacts from the working list (get_contacts_list())
4) Remove names from the working list(remove_from_contact_list(contacts_to_remove_list))
"""
def __init__(self, contact_loader):
"""
Initialize the object - a ContactLoader object is expected here
"""
self.contacts_by_group_list = contact_loader.contacts_by_group_list
self.contact_list = None

def get_group_list(self):
"""
Extract a list of all the groups.
List is in the form:
[(1, 'Group Name'), (2, 'Groups Name'), ...]
"""
return [(item[0], item[1][0]) for item in self.contacts_by_group_list]

def set_selected_group(self, group_id):
"""
Select the group to work with.

This method will make the working contact_list contain all the
contacts from the selected group.
"""
self.contact_list = self.contacts_by_group_list[group_id - 1][1][1]

# Return the contact list so far
def get_contacts_list(self):
"""
Return a list of all the contacts, and assign them each a number

List is in the form:
[(1, Contact), (2, Contact), ...]
"""
return [(id + 1, contact) for id, contact in enumerate(self.contact_list)]

def remove_from_contact_list(self, contacts_to_remove_list):
"""
Accepts a one based list of ids, indicating which contacts
to remove from the list.

List needs to be a list in ints:
[3, 6, 7]
"""
if self.contact_list is None:
return
for id in contacts_to_remove_list:
if id in range(0, len(self.contact_list) + 1):
self.contact_list[id - 1] = None
self.contact_list = [contact for contact in self.contact_list if contact is not None]

class NumberRetriever():
"""
Class that will allow you to retrieve all stored phone numbers and their aliases
"""

def __init__(self, gv_login):
"""
Pass in the GoogleVoiceLogin object, this class will then
download all the numbers and aliases of the persons GV Account
"""
self.opener = gv_login.opener
self.phone_numbers_url = 'https://www.google.com/voice/settings/tab/phones'
phone_numbers_page_content = self.opener.open(self.phone_numbers_url).read()

# Build list of all numbers and their aliases
self.phone_number_items = [(match.group(1), match.group(2))
for match
in re.finditer('"name":"([^"]+)","phoneNumber":"([^"]+)"',
phone_numbers_page_content)]

def get_phone_numbers(self):
"""
Return the list of phone numbers in the form:
[(1, number), (2, number)...]
"""
return [(id + 1, (phone_number_item))
for id, phone_number_item
in enumerate(self.phone_number_items)]

class TextSender():
"""
Class used to send text messages.

Example usage:

gv_login = GoogleVoiceLogin('username', 'password')
text_sender = TextSender(gv_login)
text_sender.text = "This is an example"
text_sender.send_text('555-555-5555')

if text_sender.response:
print "Success!"
else:
print "Fail!"
"""
def __init__(self, gv_login):
"""
Pass in a GoogleVoiceLogin object, set the text message
and then call send_text
"""
self.opener = gv_login.opener
self.key = gv_login.key
self.sms_url = 'https://www.google.com/voice/sms/send/'
self.text = ''

def send_text(self, phone_number):
"""
Sends a text message containing self.text to phone_number
"""
sms_params = urllib.urlencode({
'_rnr_se': self.key,
'phoneNumber': phone_number,
'text': self.text
})
# Send the text, display status message
self.response = "true" in self.opener.open(self.sms_url, sms_params).read()

class NumberDialer():
"""
Class used to make phone calls.

Example usage:

gv_login = GoogleVoiceLogin('username', 'password')
number_dialer = NumberDialer(gv_login)
number_dialer.forwarding_number = 'number-to-call-you-at'

number_dialer.place_call('number-to-call')

if number_dialer.response:
print "Success!"
else:
print "Fail!"
"""
def __init__(self, gv_login):
self.opener = gv_login.opener
self.key = gv_login.key
self.call_url = 'https://www.google.com/voice/call/connect/'
self.forwarding_number = None

def place_call(self, number):
"""
Pass in a GoogleVoiceLogin object, set the forwarding_number
and then call place_call('number-to-call')
"""
call_params = urllib.urlencode({
'outgoingNumber' : number,
'forwardingNumber' : self.forwarding_number,
'subscriberNumber' : 'undefined',
'remember' : '0',
'_rnr_se': self.key
})

# Send the text, display status message
self.response = self.opener.open(self.call_url, call_params).read()