<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5484413222728812890</id><updated>2012-01-30T15:54:18.273-08:00</updated><category term='_rnr_se'/><category term='google app engine'/><category term='google voice'/><category term='pygments'/><category term='interactive'/><category term='path'/><category term='login'/><category term='sms'/><category term='python'/><category term='cookies'/><category term='decorators'/><category term='pywin32'/><category term='notepad++'/><category term='sys'/><category term='windows'/><category term='regular expressions'/><category term='environment'/><category term='google voide'/><category term='command line'/><category term='drag and drop'/><category term='calling'/><category term='zip'/><category term='google'/><title type='text'>Every Day Scripting</title><subtitle type='html'>A simple blog containing every day samples of how I use Python to solve various problems - be those work problems, homework problems, or boredom problems.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>23</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-2953558448485020816</id><published>2011-10-08T19:19:00.000-07:00</published><updated>2011-10-08T19:19:59.443-07:00</updated><title type='text'>Login problems solved!</title><content type='html'>&lt;span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"&gt;Thanks to another reader (known only to me by '&lt;span class="Apple-style-span" style="background-color: white; color: #333333;"&gt;First Last') the login issues have been resolved!&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="background-color: white; color: #333333; font-family: Arial, sans-serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, sans-serif;"&gt;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&amp;nbsp;acquired&amp;nbsp;in order to bring us Google Voice).&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, sans-serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, sans-serif;"&gt;The change has been made, and the latest versions of the various Google Voices script can be &lt;a href="https://github.com/hillmanov/gvoice"&gt;downloaded from my github repo.&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, sans-serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, sans-serif;"&gt;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!).&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: Arial, sans-serif; font-size: x-small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-2953558448485020816?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/2953558448485020816/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2011/10/login-problems-solved.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/2953558448485020816'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/2953558448485020816'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2011/10/login-problems-solved.html' title='Login problems solved!'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-7256792252527456480</id><published>2011-10-01T12:25:00.000-07:00</published><updated>2011-10-02T13:27:48.022-07:00</updated><title type='text'>Login problems</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;If any of you have figured out how to do this, I would love to know!&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-7256792252527456480?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/7256792252527456480/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2011/10/login-problems.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/7256792252527456480'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/7256792252527456480'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2011/10/login-problems.html' title='Login problems'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-8934220313597850180</id><published>2011-01-05T03:53:00.000-08:00</published><updated>2011-01-05T04:12:11.265-08:00</updated><title type='text'>gvoice.py - Updated!</title><content type='html'>After too long of a wait...here is an update!&lt;br /&gt;&lt;br /&gt;Big thanks to Mike who helped me by showing me where to get the newly required "tok" variable when downloading the contact list. &lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;- gvoice.py&lt;br /&gt;- gvAllInOne.py&lt;br /&gt;- gvMassContact.py&lt;br /&gt;&lt;br /&gt;&lt;a href="https://sites.google.com/site/everydayscripting/script-source/GoogleVoice.zip?attredirects=0&amp;d=1"&gt;Get them all by downloading this Zip file&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Happy texting!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-8934220313597850180?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/8934220313597850180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2011/01/gvoicepy-updated.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/8934220313597850180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/8934220313597850180'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2011/01/gvoicepy-updated.html' title='gvoice.py - Updated!'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-5691483298098941123</id><published>2010-01-23T12:29:00.000-08:00</published><updated>2010-02-13T21:52:16.730-08:00</updated><title type='text'>Python - Easy jQuery lightBox Gallery</title><content type='html'>If you ever have to put images up on the internet, I recommend using the excellent &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; plugin &lt;a href="http://leandrovieira.com/projects/jquery/lightbox/"&gt;lightBox&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;Fist off, however, you will need to install the &lt;a href="http://www.pythonware.com/products/pil/"&gt;Python Image Library (PIL)&lt;/a&gt;. This handles the resizing part of the script.&lt;br /&gt;You will also need jQuery included on your page, as well as everything needed for lightBox.&lt;br /&gt;&lt;br /&gt;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)&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import os&lt;br /&gt;from PIL import Image&lt;br /&gt;import sys&lt;br /&gt;import glob&lt;br /&gt;&lt;br /&gt;try:&lt;br /&gt; base_dir =  '&amp;lt;insert the full path to the root directory of your site (eg. c:\\sites\\myawesomesite\\)&amp;gt;'&lt;br /&gt; img_dir = base_dir + '&amp;lt;insert the folder where you keep your images (based off the base_dir defined above (eg. \\images\\)&amp;gt;'&lt;br /&gt; thumbs_dir = img_dir + '&amp;lt;insert the folder where you want the thumbnails placed, relative to the images dir defined above (eg. thumbs\\)&amp;gt;'&lt;br /&gt; originals_dir = img_dir + '&amp;lt;insert the folder where you want the originals placed, relative to the images dir defined above (eg. originals\\)&amp;gt;'&lt;br /&gt; web_base_path = '&amp;lt;put the relative URL path to the images directory (eg. /images/)&amp;gt;'&lt;br /&gt; web_thumbs_path = web_base_path + '&amp;lt;put the relative URL path to the thumbs directory (relative to the images dir defined above) (eg. thumbs/)&amp;gt;'&lt;br /&gt;&lt;br /&gt; temp_gallery_file = base_dir + '&amp;lt;put the name of the file where you want the gallery code to be stored (eg. TEMP_GALLERY_FILE.html)&amp;gt;'&lt;br /&gt;&lt;br /&gt; column_amount = 3 # Put how many columns wide you want the gallery to be&lt;br /&gt;&lt;br /&gt; img_display = """\t\t&amp;lt;td&amp;gt;&lt;br /&gt;         &amp;lt;div class="image"&amp;gt;&lt;br /&gt;             &amp;lt;a href="{0}" title=""&amp;gt;&lt;br /&gt;                 &amp;lt;img src="{1}" alt="" /&amp;gt;&lt;br /&gt;             &amp;lt;/a&amp;gt;&lt;br /&gt;             &amp;lt;span class="description"&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;         &amp;lt;/div&amp;gt;&lt;br /&gt;         &amp;lt;br /&amp;gt;&lt;br /&gt;     &amp;lt;/td&amp;gt;&lt;br /&gt; """&lt;br /&gt;   &lt;br /&gt; if len(sys.argv) == 1:&lt;br /&gt;  sys.exit(1)&lt;br /&gt;&lt;br /&gt; resource = sys.argv[1] &lt;br /&gt;&lt;br /&gt; # Default width, if it is less, than the old width is used.&lt;br /&gt; thumb_width = 200&lt;br /&gt; max_orignal_width = 700&lt;br /&gt;&lt;br /&gt; # Keep the aspect ration (based on the width of the image)&lt;br /&gt; def aspectRatioSize(desired_width, original_size):&lt;br /&gt;  original_width = original_size[0] + .0&lt;br /&gt;  original_height = original_size[1] + .0&lt;br /&gt;  return (desired_width, desired_width * (original_height / original_width))&lt;br /&gt;  &lt;br /&gt; def getNextAvailableName(folder, img_name, ext):&lt;br /&gt;  index = 1&lt;br /&gt;  while os.path.exists(folder + img_name + '_' + str(index) + ext):&lt;br /&gt;   index += 1&lt;br /&gt;  return img_name + '_' + str(index)+ ext&lt;br /&gt;&lt;br /&gt; gallery_file = open(temp_gallery_file, "w") &lt;br /&gt;&lt;br /&gt; gallery_file.write("&amp;lt;div class=\"gallery\"&amp;gt;\n")&lt;br /&gt; gallery_file.write("&amp;lt;table&amp;gt;\n")&lt;br /&gt; gallery_file.write("\t&amp;lt;tr&amp;gt;\n")&lt;br /&gt;&lt;br /&gt; base_image_name = os.path.basename(resource)&lt;br /&gt; images = [image for image in glob.glob(resource + "/*.*")]&lt;br /&gt; # Loop through all images in directory, make thumbs and resize the original &lt;br /&gt; for index, image in enumerate(images):&lt;br /&gt;  index += 1&lt;br /&gt;  print image&lt;br /&gt;  img_name, ext = os.path.splitext(os.path.basename(image))&lt;br /&gt;  im = Image.open(image)&lt;br /&gt;  img_size = im.size&lt;br /&gt;  thumb_name = getNextAvailableName(thumbs_dir,  base_image_name, ext)&lt;br /&gt;  modified_large_name = getNextAvailableName(img_dir, base_image_name , ext)&lt;br /&gt;  original_name = getNextAvailableName(originals_dir, base_image_name, ext)&lt;br /&gt;  &lt;br /&gt;  # Thumbnail&lt;br /&gt;  thumb = im.resize(aspectRatioSize(thumb_width, img_size), Image.ANTIALIAS)&lt;br /&gt;  thumb.save(thumbs_dir + thumb_name)&lt;br /&gt;  &lt;br /&gt;  # Modified Large image&lt;br /&gt;  modified_large = im.resize(aspectRatioSize(max_orignal_width, img_size), Image.ANTIALIAS)&lt;br /&gt;  modified_large.save(img_dir + modified_large_name)&lt;br /&gt;  &lt;br /&gt;  # Original&lt;br /&gt;  im.save(originals_dir + original_name)&lt;br /&gt;  &lt;br /&gt;  if index != 1 and index % column_amount == 1:&lt;br /&gt;   gallery_file.write("\t&amp;lt;tr&amp;gt;\n")&lt;br /&gt;   &lt;br /&gt;  gallery_file.write(img_display.format(web_base_path + modified_large_name, web_thumbs_path + thumb_name))&lt;br /&gt;  &lt;br /&gt;  if not index % column_amount:&lt;br /&gt;   gallery_file.write("\t&amp;lt;/tr&amp;gt;\n")&lt;br /&gt;  &lt;br /&gt; if index % column_amount &amp;gt; 0:&lt;br /&gt;  gallery_file.write("\t&amp;lt;/tr&amp;gt;\n")&lt;br /&gt;  &lt;br /&gt; gallery_file.write("&amp;lt;/table&amp;gt;\n")&lt;br /&gt; gallery_file.write("&amp;lt;/div&amp;gt;")&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; gallery_file.close()&lt;br /&gt;except Exception as e:&lt;br /&gt; print e&lt;br /&gt; raw_input()&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I also use the following CSS to make the gallery, once created, look the way I want.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;.gallery .image {&lt;br /&gt;    margin: 0 auto;&lt;br /&gt;    text-align: center;&lt;br /&gt;    vertical-align: top;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;.gallery .image .description {&lt;br /&gt;    font-size: 11px;&lt;br /&gt;    color: #000066;&lt;br /&gt;    text-align: center;&lt;br /&gt;    display: block;&lt;br /&gt;    line-height: normal !important;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;.gallery td {&lt;br /&gt;    vertical-align: top;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;.gallery table {&lt;br /&gt;    margin: 0 auto;&lt;br /&gt;    width: 90%&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;.gallery img {&lt;br /&gt;    border: none;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;I will be posting a demonstration video shortly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-5691483298098941123?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/5691483298098941123/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2010/01/python-easy-jquery-lightbox-gallery.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/5691483298098941123'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/5691483298098941123'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2010/01/python-easy-jquery-lightbox-gallery.html' title='Python - Easy jQuery lightBox Gallery'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-1587191980168491285</id><published>2009-12-26T08:05:00.000-08:00</published><updated>2009-12-26T08:19:38.149-08:00</updated><title type='text'>Python - Google App Engine + Boggle Solver + Web2Py</title><content type='html'>&lt;a href="http://pyboggle.appspot.com"&gt;Click here to go right to the solver&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Lately I have been experimenting with a Python web development framework called &lt;a href="http://web2py.com/"&gt;Web2Py&lt;/a&gt;, 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.&lt;br /&gt;&lt;br /&gt;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 &lt;a href="http://web2py.com/"&gt;Home Page&lt;/a&gt; to find out more).&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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. &lt;a href="http://pyboggle.appspot.com/"&gt;The resulting Boggle Solver can be found here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Things to note about the solver:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt; &lt;li&gt;I wrote this app because I was unhappy with all the other online boggle solvers out there&lt;/li&gt;&lt;li&gt;&lt;br /&gt; &lt;/li&gt;&lt;li&gt;For the random boards, I use actual Boggle dice configurations not just random letters&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Written completey in Python using the &lt;a href="http://web2py.com/" target="_blank"&gt;Web2Py&lt;/a&gt; framework, running on the Google App Engine&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Uses a dictionary of 170+ words (I am pretty sure it is the Enable2k dictionary)&lt;/li&gt;&lt;br /&gt; &lt;li&gt;Visit my blog &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-boggle-solver.html"&gt;to see how the words are found&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Enjoy!&lt;br /&gt;&lt;br /&gt;PS: If you are a CS student somewhere and you have this same assignment: Do your own work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-1587191980168491285?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/1587191980168491285/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/12/python-google-app-engine-boggle-solver.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1587191980168491285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1587191980168491285'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/12/python-google-app-engine-boggle-solver.html' title='Python - Google App Engine + Boggle Solver + Web2Py'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-6115887192504355848</id><published>2009-10-09T23:32:00.000-07:00</published><updated>2010-01-23T12:26:33.416-08:00</updated><title type='text'>Python - Custom Google Voice API - Installable Module</title><content type='html'>Google Voice is a great service, but it lacks a couple of important things:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;An official API from Google&lt;br /&gt;&lt;/li&gt;&lt;li&gt;A way to send mass SMS messages&lt;/li&gt;&lt;/ol&gt;I have spend some time creating my own &lt;span style="font-weight: bold;"&gt;unofficial &lt;/span&gt;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 "&lt;a href="http://sites.google.com/site/everydayscripting/script-source/gvoice.zip?attredirects=0"&gt;gvoice.py&lt;/a&gt;" script that I wrote, which contains several helpful classes. These class allow you to perform some basic actions, such as:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt; Logging in to your Google Voice Account&lt;/li&gt;&lt;li&gt;Sending a text message&lt;/li&gt;&lt;li&gt;Placing a phone call&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;The script also allows you to:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Download your Google Contacts into memory (Used mainly to get contact information to call or send a message)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Programmatically select from you Contact Groups which people to contact&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Retrieve the numbers you have set up to work with your Google Voice account&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Currently the script has only been tested using Python 2.6 (Python 3 might work, but I am not sure).&lt;br /&gt;&lt;a name="downloads"&gt;&lt;/a&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://sites.google.com/site/everydayscripting/script-source/gvoice.zip?attredirects=0"&gt;gvoice.py&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Here are some of the posts where I show examples on how to use this module:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;Python + Google Voice. Mass SMS and Iterative Calling at the Command Line&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://everydayscripting.blogspot.com/2009/10/python-google-voice-from-command-line.html"&gt;Python - Google Voice from the command line (CLI SMS and Calling)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;And the complete source:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;"""&lt;br /&gt;gvoice.py&lt;br /&gt;&lt;br /&gt;Created by: Scott Hillman&lt;br /&gt;&lt;br /&gt;http://www.everydayscripting.blogspot.com&lt;br /&gt;&lt;br /&gt;This module comes as is an with no warranty. &lt;br /&gt;You are free to use, modify and distribute this &lt;br /&gt;code however you wish, but I ask that if you post &lt;br /&gt;it anywhere, you at least make reference to me and&lt;br /&gt;my blog where you acquired it.&lt;br /&gt;"""&lt;br /&gt;&lt;br /&gt;import csv&lt;br /&gt;import sys&lt;br /&gt;import re&lt;br /&gt;import urllib&lt;br /&gt;import urllib2&lt;br /&gt;&lt;br /&gt;class GoogleVoiceLogin:&lt;br /&gt;   """ &lt;br /&gt;    Class that attempts to log in the Google Voice     using the provided &lt;br /&gt;    credentials. &lt;br /&gt;    &lt;br /&gt;    If either no password or email is provided, the user will be &lt;br /&gt;    prompted for them.&lt;br /&gt;    &lt;br /&gt;    Once instantiated, you can check to see the status of the log in &lt;br /&gt;    request by accessing the "logged_in" attribute&lt;br /&gt;    &lt;br /&gt;    The primary usage of a GoogleVoiceLogin object is to be passed&lt;br /&gt;    in to other constructors, such as the TextSender, or NumberDialer&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;   def __init__(self, email = None, password = None):&lt;br /&gt;       """&lt;br /&gt;        Given the email and password values, this method will attempt to log&lt;br /&gt;        in to Google Voice. The "response" attribute can be checked to &lt;br /&gt;        see if the login was a success or not.&lt;br /&gt;         &lt;br /&gt;        If the login was successful, the "opener" and "key" attributes will&lt;br /&gt;        be available to use when creating other objects. &lt;br /&gt;        &lt;br /&gt;        To use an this object with the other classes in this module, simply&lt;br /&gt;        pass it in to the constructor. (ie text_sender = TextSender(gv_login))&lt;br /&gt;        """&lt;br /&gt;&lt;br /&gt;       if email is None:&lt;br /&gt;           email = raw_input("Please enter your Google Account username: ")&lt;br /&gt;       if password is None:&lt;br /&gt;           import getpass&lt;br /&gt;           password = getpass.getpass("Please enter your Google Account password: ")&lt;br /&gt;&lt;br /&gt;       # Set up our opener&lt;br /&gt;       self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;       urllib2.install_opener(self.opener)&lt;br /&gt;&lt;br /&gt;       # Define URLs&lt;br /&gt;       self.login_page_url = 'https://www.google.com/accounts/ServiceLogin'&lt;br /&gt;       self.authenticate_url = 'https://www.google.com/accounts/ServiceLoginAuth'&lt;br /&gt;       self.gv_home_page_url = 'https://www.google.com/voice/#inbox'&lt;br /&gt;&lt;br /&gt;       # Load sign in page&lt;br /&gt;       login_page_contents = self.opener.open(self.login_page_url).read()&lt;br /&gt;&lt;br /&gt;       # Find GALX value&lt;br /&gt;       galx_match_obj = re.search(r'name="GALX"\s*value="([^"]+)"', login_page_contents, re.IGNORECASE)&lt;br /&gt;&lt;br /&gt;       galx_value = galx_match_obj.group(1) if galx_match_obj.group(1) is not None else ''&lt;br /&gt;&lt;br /&gt;       # Set up login credentials&lt;br /&gt;       login_params = urllib.urlencode({&lt;br /&gt;           'Email' : email,&lt;br /&gt;           'Passwd' : password,&lt;br /&gt;           'continue' : 'https://www.google.com/voice/account/signin',&lt;br /&gt;           'GALX': galx_value&lt;br /&gt;       })&lt;br /&gt;&lt;br /&gt;       # Login&lt;br /&gt;       self.opener.open(self.authenticate_url, login_params)&lt;br /&gt;&lt;br /&gt;       # Open GV home page&lt;br /&gt;       gv_home_page_contents = self.opener.open(self.gv_home_page_url).read()&lt;br /&gt;&lt;br /&gt;       # Fine _rnr_se value&lt;br /&gt;       key = re.search('name="_rnr_se".*?value="(.*?)"', gv_home_page_contents)&lt;br /&gt;&lt;br /&gt;       if not key:&lt;br /&gt;           self.logged_in = False&lt;br /&gt;       else:&lt;br /&gt;           self.logged_in = True&lt;br /&gt;           self.key = key.group(1)&lt;br /&gt;&lt;br /&gt;class ContactLoader():&lt;br /&gt;   """ &lt;br /&gt;    This class is used to download and organize a csv file &lt;br /&gt;    of Google Contacts.It is often used in conjunction with &lt;br /&gt;    the ContactSelector class.&lt;br /&gt;    &lt;br /&gt;    Example:&lt;br /&gt;    &lt;br /&gt;    contact_loader = ContactLoader(gv_login)&lt;br /&gt;    contact_selector = ContactSelector(contact_loader)&lt;br /&gt;    """&lt;br /&gt;   def __init__(self, gv_login):&lt;br /&gt;       """ &lt;br /&gt;        Pass in a GoogleVoiceLogin object, and the persons Google Contacts&lt;br /&gt;        Will be downloaded and organized into a structure called &lt;br /&gt;        "contacts_by_group_list"&lt;br /&gt;        which is organized in form:&lt;br /&gt;        &lt;br /&gt;        [(1, ('group_name', [contact_list])), (2, ('group_name', [contact_list]))]&lt;br /&gt;        &lt;br /&gt;        Which allows for easy access to any group. &lt;br /&gt;        """&lt;br /&gt;       self.opener = gv_login.opener&lt;br /&gt;       self.contacts_csv_url = "http://mail.google.com/mail/contacts/data/export"&lt;br /&gt;       self.contacts_csv_url += "?groupToExport=^Mine&amp;exportType=ALL&amp;out=OUTLOOK_CSV"&lt;br /&gt;&lt;br /&gt;       # Load ALL Google Contacts into csv dictionary&lt;br /&gt;       self.contacts = csv.DictReader(self.opener.open(self.contacts_csv_url))&lt;br /&gt;&lt;br /&gt;       # Create dictionary to store contacts and groups in an easier format&lt;br /&gt;       self.contact_group = {}&lt;br /&gt;       # Assigned each person to a group that we can get at later&lt;br /&gt;       for row in self.contacts:&lt;br /&gt;           if row['First Name'] != '':&lt;br /&gt;               for category in row['Categories'].split(';'):&lt;br /&gt;                   if category == '':&lt;br /&gt;                       category = 'Ungrouped'&lt;br /&gt;                   if category not in self.contact_group:&lt;br /&gt;                       self.contact_group[category] = [Contact(row)]&lt;br /&gt;                   else:&lt;br /&gt;                       self.contact_group[category].append(Contact(row))&lt;br /&gt;&lt;br /&gt;       # Load contacts into a list of tuples... &lt;br /&gt;       # [(1, ('group_name', [contact_list])), (2, ('group_name', [contact_list]))]&lt;br /&gt;       self.contacts_by_group_list = [(id + 1, group_contact_item)&lt;br /&gt;                                      for id, group_contact_item in enumerate(self.contact_group.items())]&lt;br /&gt;&lt;br /&gt;class Contact():&lt;br /&gt;   """ &lt;br /&gt;    Simple class to contain information on each Google Contact person.&lt;br /&gt;    &lt;br /&gt;    Only stores information on:&lt;br /&gt;    First Name&lt;br /&gt;    Last Name&lt;br /&gt;    Mobile Number&lt;br /&gt;    Email address&lt;br /&gt;    """&lt;br /&gt;   def __init__(self, contact_detail):&lt;br /&gt;       """ &lt;br /&gt;        Extract data from the given contact_detail&lt;br /&gt;        &lt;br /&gt;        The following attributes are available:&lt;br /&gt;        &lt;br /&gt;        first_name&lt;br /&gt;        last_name&lt;br /&gt;        mobile&lt;br /&gt;        email&lt;br /&gt;        """&lt;br /&gt;       self.first_name = contact_detail['First Name'].strip()&lt;br /&gt;       self.last_name = contact_detail['Last Name'].strip()&lt;br /&gt;       self.mobile = contact_detail['Mobile Phone'].strip()&lt;br /&gt;       self.email = contact_detail['E-mail Address'].strip()&lt;br /&gt;&lt;br /&gt;   def __str__(self):&lt;br /&gt;       return self.first_name + ' ' + self.last_name&lt;br /&gt;&lt;br /&gt;# Class to assist in selected contacts by groups &lt;br /&gt;class ContactSelector():&lt;br /&gt;   """&lt;br /&gt;    Class with helps select contacts after using the ContactLoader&lt;br /&gt;    object to download them.&lt;br /&gt;    &lt;br /&gt;    Provides methods to:&lt;br /&gt;    1) Display the list of groups (get_group_list())&lt;br /&gt;    2) Set the selected group to work with (set_selected_group(group_id))&lt;br /&gt;    3) Get the contacts from the working list (get_contacts_list())&lt;br /&gt;    4) Remove names from the working list(remove_from_contact_list(contacts_to_remove_list))&lt;br /&gt;    """&lt;br /&gt;   def __init__(self, contact_loader):&lt;br /&gt;       """&lt;br /&gt;        Initialize the object - a ContactLoader object is expected here&lt;br /&gt;        """&lt;br /&gt;       self.contacts_by_group_list = contact_loader.contacts_by_group_list&lt;br /&gt;       self.contact_list = None&lt;br /&gt;&lt;br /&gt;   def get_group_list(self):&lt;br /&gt;       """&lt;br /&gt;        Extract a list of all the groups. &lt;br /&gt;        List is in the form:&lt;br /&gt;        [(1, 'Group Name'), (2, 'Groups Name'), ...]&lt;br /&gt;        """&lt;br /&gt;       return [(item[0], item[1][0]) for item in self.contacts_by_group_list]&lt;br /&gt;&lt;br /&gt;   def set_selected_group(self, group_id):&lt;br /&gt;       """&lt;br /&gt;        Select the group to work with. &lt;br /&gt;&lt;br /&gt;        This method will make the working contact_list contain all the&lt;br /&gt;        contacts from the selected group. &lt;br /&gt;        """&lt;br /&gt;       self.contact_list = self.contacts_by_group_list[group_id - 1][1][1]&lt;br /&gt;&lt;br /&gt;   # Return the contact list so far&lt;br /&gt;   def get_contacts_list(self):&lt;br /&gt;       """&lt;br /&gt;        Return a list of all the contacts, and assign them each a number&lt;br /&gt;&lt;br /&gt;        List is in the form:&lt;br /&gt;        [(1, Contact), (2, Contact), ...]&lt;br /&gt;        """&lt;br /&gt;       return [(id + 1, contact) for id, contact in enumerate(self.contact_list)]&lt;br /&gt;&lt;br /&gt;   def remove_from_contact_list(self, contacts_to_remove_list):&lt;br /&gt;       """&lt;br /&gt;         Accepts a one based list of ids, indicating which contacts&lt;br /&gt;         to remove from the list.&lt;br /&gt;         &lt;br /&gt;         List needs to be a list in ints:&lt;br /&gt;         [3, 6, 7]&lt;br /&gt;        """&lt;br /&gt;       if self.contact_list is None:&lt;br /&gt;           return&lt;br /&gt;       for id in contacts_to_remove_list:&lt;br /&gt;           if id in range(0, len(self.contact_list) + 1):&lt;br /&gt;               self.contact_list[id - 1] = None&lt;br /&gt;       self.contact_list = [contact for contact in self.contact_list if contact is not None]&lt;br /&gt;&lt;br /&gt;class NumberRetriever():&lt;br /&gt;   """&lt;br /&gt;    Class that will allow you to retrieve all stored phone numbers and their aliases&lt;br /&gt;    """&lt;br /&gt;&lt;br /&gt;   def __init__(self, gv_login):&lt;br /&gt;       """&lt;br /&gt;        Pass in the GoogleVoiceLogin object, this class will then&lt;br /&gt;        download all the numbers and aliases of the persons GV Account&lt;br /&gt;        """&lt;br /&gt;       self.opener = gv_login.opener&lt;br /&gt;       self.phone_numbers_url = 'https://www.google.com/voice/settings/tab/phones'&lt;br /&gt;       phone_numbers_page_content = self.opener.open(self.phone_numbers_url).read()&lt;br /&gt;&lt;br /&gt;       # Build list of all numbers and their aliases&lt;br /&gt;       self.phone_number_items = [(match.group(1), match.group(2))&lt;br /&gt;                                  for match&lt;br /&gt;                                  in re.finditer('"name":"([^"]+)","phoneNumber":"([^"]+)"',&lt;br /&gt;                                                           phone_numbers_page_content)]&lt;br /&gt;&lt;br /&gt;   def get_phone_numbers(self):&lt;br /&gt;       """&lt;br /&gt;        Return the list of phone numbers in the form:&lt;br /&gt;        [(1, number), (2, number)...]&lt;br /&gt;        """&lt;br /&gt;       return [(id + 1, (phone_number_item))&lt;br /&gt;               for id, phone_number_item&lt;br /&gt;               in enumerate(self.phone_number_items)]&lt;br /&gt;&lt;br /&gt;class TextSender():&lt;br /&gt;   """&lt;br /&gt;    Class used to send text messages.&lt;br /&gt;    &lt;br /&gt;    Example usage:&lt;br /&gt;    &lt;br /&gt;    gv_login = GoogleVoiceLogin('username', 'password')&lt;br /&gt;    text_sender = TextSender(gv_login)&lt;br /&gt;    text_sender.text = "This is an example"&lt;br /&gt;    text_sender.send_text('555-555-5555')&lt;br /&gt;    &lt;br /&gt;    if text_sender.response:&lt;br /&gt;        print "Success!"&lt;br /&gt;     else:&lt;br /&gt;        print "Fail!"&lt;br /&gt;    """&lt;br /&gt;   def __init__(self, gv_login):&lt;br /&gt;       """ &lt;br /&gt;        Pass in a GoogleVoiceLogin object, set the text message &lt;br /&gt;        and then call send_text&lt;br /&gt;        """&lt;br /&gt;       self.opener = gv_login.opener&lt;br /&gt;       self.key = gv_login.key&lt;br /&gt;       self.sms_url = 'https://www.google.com/voice/sms/send/'&lt;br /&gt;       self.text = ''&lt;br /&gt;&lt;br /&gt;   def send_text(self, phone_number):&lt;br /&gt;       """&lt;br /&gt;        Sends a text message containing self.text to phone_number&lt;br /&gt;        """&lt;br /&gt;       sms_params = urllib.urlencode({&lt;br /&gt;           '_rnr_se': self.key,&lt;br /&gt;           'phoneNumber': phone_number,&lt;br /&gt;           'text': self.text&lt;br /&gt;       })&lt;br /&gt;       # Send the text, display status message  &lt;br /&gt;       self.response = "true" in self.opener.open(self.sms_url, sms_params).read()&lt;br /&gt;&lt;br /&gt;class NumberDialer():&lt;br /&gt;   """ &lt;br /&gt;    Class used to make phone calls.&lt;br /&gt;&lt;br /&gt;    Example usage:&lt;br /&gt;&lt;br /&gt;    gv_login = GoogleVoiceLogin('username', 'password')&lt;br /&gt;    number_dialer = NumberDialer(gv_login)&lt;br /&gt;    number_dialer.forwarding_number = 'number-to-call-you-at'&lt;br /&gt;&lt;br /&gt;    number_dialer.place_call('number-to-call')&lt;br /&gt;&lt;br /&gt;    if number_dialer.response:&lt;br /&gt;        print "Success!"&lt;br /&gt;     else:&lt;br /&gt;        print "Fail!"&lt;br /&gt;    """&lt;br /&gt;   def __init__(self, gv_login):&lt;br /&gt;       self.opener = gv_login.opener&lt;br /&gt;       self.key = gv_login.key&lt;br /&gt;       self.call_url = 'https://www.google.com/voice/call/connect/'&lt;br /&gt;       self.forwarding_number = None&lt;br /&gt;&lt;br /&gt;   def place_call(self, number):&lt;br /&gt;       """ &lt;br /&gt;        Pass in a GoogleVoiceLogin object, set the forwarding_number&lt;br /&gt;        and then call place_call('number-to-call')&lt;br /&gt;        """&lt;br /&gt;       call_params = urllib.urlencode({&lt;br /&gt;           'outgoingNumber' : number,&lt;br /&gt;           'forwardingNumber' : self.forwarding_number,&lt;br /&gt;           'subscriberNumber' : 'undefined',&lt;br /&gt;           'remember' : '0',&lt;br /&gt;           '_rnr_se': self.key&lt;br /&gt;       })&lt;br /&gt;&lt;br /&gt;       # Send the text, display status message  &lt;br /&gt;       self.response = self.opener.open(self.call_url, call_params).read()&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-6115887192504355848?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/6115887192504355848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/10/python-custom-google-voice-api.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6115887192504355848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6115887192504355848'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/10/python-custom-google-voice-api.html' title='Python - Custom Google Voice API - Installable Module'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-1236270776124690735</id><published>2009-10-08T23:22:00.000-07:00</published><updated>2010-04-25T18:55:01.398-07:00</updated><title type='text'>Python - Google Voice from the command line (CLI SMS and Calling)</title><content type='html'>I have written about Google Voice a few times already, but the script that gets the most attention is the one that allows you to mass text/call people in your Google Contacts Groups, which you can find &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Many people do not need such a large script, since they simply want to be able to use something to send one text message at a time through Google Voice &lt;span style="font-weight: bold;"&gt;at the command line&lt;/span&gt;. Even though my scripts were written with mass contacting in mind, you can still use my gvoice module to easily contact just one person.&lt;br /&gt;&lt;br /&gt;To use this script, you &lt;span style="font-weight: bold;"&gt;will need to download my gvoice module&lt;/span&gt;. There are instructions about installing this module &lt;a href="http://everydayscripting.blogspot.com/2009/10/python-custom-google-voice-api.html"&gt;here&lt;/a&gt;, as well as some documentation on it.&lt;br /&gt;&lt;br /&gt;Once you have the &lt;a href="http://sites.google.com/site/everydayscripting/script-source/gvoice.zip?attredirects=0"&gt;gvoice&lt;/a&gt; module installed, creating a command line script to text someone is trivial:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import gvoice&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;if len(sys.argv) &lt; 3:&lt;br /&gt;    print "You must provide both a number and a message!"&lt;br /&gt;    exit()&lt;br /&gt;&lt;br /&gt;gv_login = gvoice.GoogleVoiceLogin('username', 'password')&lt;br /&gt;text_sender = gvoice.TextSender(gv_login)&lt;br /&gt;&lt;br /&gt;text_sender.text = sys.argv[2]&lt;br /&gt;text_sender.send_text(sys.argv[1])&lt;br /&gt;&lt;br /&gt;if text_sender.response:&lt;br /&gt;    print 'Success! Message sent!'&lt;br /&gt;else:&lt;br /&gt;    print 'Failure! Message not sent!'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice in this script I am explicitly setting the username and password. If you leave out those parameters, you will be prompted for them at run time.&lt;br /&gt;&lt;br /&gt;You can now call the script (which I am naming"smser.py") like so:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;python smser.py "555-555-5555" "This was sent from the command line!"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you want to make a call to someone, it is just as easy:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import gvoice&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;if len(sys.argv) &lt; 3:&lt;br /&gt;    print "You must provide both a forwarding number and number to dial!"&lt;br /&gt;    exit()&lt;br /&gt;&lt;br /&gt;gv_login = gvoice.GoogleVoiceLogin('username', 'password')&lt;br /&gt;number_dialer = gvoice.NumberDialer(gv_login)&lt;br /&gt;number_dialer.forwarding_number = sys.argv[2]&lt;br /&gt;number_dialer.place_call(sys.argv[1])&lt;br /&gt;&lt;br /&gt;if number_dialer.response:&lt;br /&gt;   print 'Success! You should hear the phone ringing shortly...'&lt;br /&gt;else:&lt;br /&gt;   print 'Call failed!'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You would use this script (which I am naming "caller.py") much the same way:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;python caller.py "number-to-dial" "number-to-call-you-at"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Just remember the order of the numbers there, and you should be good to go.&lt;br /&gt;&lt;br /&gt;Why would this CLI be helpful to you if you have the nice GUI of Google Voice? I can think of at least two good reasons:&lt;br /&gt;&lt;br /&gt;1) It is faster to do at the command line (If you know the numbers!)&lt;br /&gt;2) You can automate the sending of a text message. One reader shared with me the idea of executing something at the command line that would take a long time, and chaining on the the end of it "smser.py 'my-number' 'Finished!'", so he could walk away from his computer and know when the command had finished up. (Thanks Shane!) You could also set up cron jobs to send out a message on a given day and time (Another friend used this to wish himself Happy Birthday)&lt;br /&gt;&lt;br /&gt;If you are on a Windows machine, you can add the folder where you download these scripts to your PATH environment variable so you can access them anywhere. If you want to go one step further and make it so you don't even have to type in "python" before the file name or ".py" after the file name when executing a Python script, follow the instructions &lt;a href="http://everydayscripting.blogspot.com/2009/08/easier-python-script-execution-in.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;If you are in Linux, you should be able to create a symbolic link in /usr/bin if you want to achieve the same thing.&lt;br /&gt;&lt;br /&gt;As usual, here is a zip file containing the source code of the examples shown above, plus the examples shown in the respective files. I also threw in there the installable gvoice module and the standalone gvoice module with some brief instructions on how to install either.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://sites.google.com/site/everydayscripting/script-source/gv_cli_examples.zip?attredirects=0"&gt;gv_cli_examples.zip&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Let me know if you have any questions or problems using these scripts.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-1236270776124690735?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/1236270776124690735/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/10/python-google-voice-from-command-line.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1236270776124690735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1236270776124690735'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/10/python-google-voice-from-command-line.html' title='Python - Google Voice from the command line (CLI SMS and Calling)'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-8041014856344546216</id><published>2009-10-02T23:54:00.000-07:00</published><updated>2010-01-23T12:22:03.824-08:00</updated><title type='text'>Python - Fixes to the Google Login script</title><content type='html'>Just recently Google changed some things behind the scenes to their login page. More specifically, on the login page there is now a hidden form element called "GALX", the value of which must be passed along with the Username and Password when signing in - other wise, the sign in fails. This affected a few of my scripts, most notably my &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;Google Voice Mass Contact&lt;/a&gt; script which a few visitors let me know about.&lt;br /&gt;&lt;br /&gt;I modified the scripts that are contained in that post to work with the new system. &lt;span style="font-weight: bold; color: rgb(0, 0, 153);"&gt;So, if you have been having trouble with the mass contact script, go to &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;that post&lt;/a&gt; and re-download the scripts.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here is the complete login script, including how to get the _rnr_se value after logging in.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import urllib&lt;br /&gt;import urllib2&lt;br /&gt;import getpass&lt;br /&gt;import re&lt;br /&gt;&lt;br /&gt;email = raw_input("Enter your Google username: ")&lt;br /&gt;password = getpass.getpass("Enter your password: ")&lt;br /&gt;&lt;br /&gt;opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(opener)&lt;br /&gt;&lt;br /&gt;# Define URLs&lt;br /&gt;loing_page_url = 'https://www.google.com/accounts/ServiceLogin'&lt;br /&gt;authenticate_url = 'https://www.google.com/accounts/ServiceLoginAuth'&lt;br /&gt;gv_home_page_url = 'https://www.google.com/voice/#inbox'&lt;br /&gt;&lt;br /&gt;# Load sign in page&lt;br /&gt;login_page_contents = opener.open(loing_page_url).read()&lt;br /&gt;&lt;br /&gt;# Find GALX value&lt;br /&gt;galx_match_obj = re.search(r'name="GALX"\s*value="([^"]+)"', login_page_contents, re.IGNORECASE)&lt;br /&gt;&lt;br /&gt;galx_value = galx_match_obj.group(1) if galx_match_obj.group(1) is not None else ''&lt;br /&gt;&lt;br /&gt;# Set up login credentials&lt;br /&gt;login_params = urllib.urlencode( {&lt;br /&gt;   'Email' : email,&lt;br /&gt;   'Passwd' : password,&lt;br /&gt;   'continue' : 'https://www.google.com/voice/account/signin',&lt;br /&gt;   'GALX': galx_value&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;# Login&lt;br /&gt;opener.open(authenticate_url, login_params)&lt;br /&gt;&lt;br /&gt;# Open GV home page&lt;br /&gt;gv_home_page_contents = opener.open(gv_home_page_url).read()&lt;br /&gt;&lt;br /&gt;# Fine _rnr_se value&lt;br /&gt;key = re.search('name="_rnr_se".*?value="(.*?)"', gv_home_page_contents)&lt;br /&gt;&lt;br /&gt;if not key:&lt;br /&gt;   logged_in = False&lt;br /&gt;   print 'Failed!'&lt;br /&gt;else:&lt;br /&gt;   logged_in = True&lt;br /&gt;   key = key.group(1)&lt;br /&gt;   print 'Success!'&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-8041014856344546216?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/8041014856344546216/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/10/python-fixes-to-google-login-script.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/8041014856344546216'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/8041014856344546216'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/10/python-fixes-to-google-login-script.html' title='Python - Fixes to the Google Login script'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-6480147251342246903</id><published>2009-09-18T19:18:00.000-07:00</published><updated>2010-02-11T06:40:16.991-08:00</updated><title type='text'>Python - Back to Basics: Accessing Online Content</title><content type='html'>Many of the different scripts that I write in Python deal with accessing content online. I thought that it might be a good idea to go over some of the different methods Python provides,  and give some examples of their every day uses.&lt;br /&gt;&lt;br /&gt;Most of the functionality you may ever want to access online content can be found in one of two modules (and often you need to use both) : &lt;a href="http://docs.python.org/library/urllib.html"&gt;urllib&lt;/a&gt; and &lt;a href="http://docs.python.org/library/urllib2.html"&gt;urllib2&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;First situation: You want to download the content of some page, perhaps to scrape the page for information for later processing. You want the content to be stored in a single variable (a string) so that you can do some regular expression matching on it, or some other form of parsing. A few simple lines:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import urllib2&lt;br /&gt;&lt;br /&gt;page_contents = urllib2.urlopen("http://www.google.com").read()&lt;br /&gt;&lt;br /&gt;print page_contents&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Notice how on the second line I have the ".read()" method call. If you leave this off, you will have just the socket file object, which you can later call ".read()" on to get the contents. I like to do it all in one line however. Once you do this, it is a regular string and you can do whatever you want to/with it.&lt;br /&gt;&lt;br /&gt;Now, lets say that you don't want to just download the page contents - you want to download an actual file, like an image, video, Power Point Presentation, PDF or anything else. Also, just a few lines:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&lt;br /&gt;# Download the Python image from http://python.org&lt;br /&gt;import urllib&lt;br /&gt;&lt;br /&gt;# File will be called "Python-Logo.gif",and will be contained in the folder&lt;br /&gt;# where the script was executed&lt;br /&gt;urllib.urlretrieve("http://python.org/images/python-logo.gif", "Python-Logo.gif")&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can use the "&lt;a href="http://docs.python.org/library/urllib.html#urllib.urlretrieve"&gt;urlretrive&lt;/a&gt;" method to download pretty much any file you want - you just need to know it's URL and what you want to call it when you download it. Keep in mind that if you don't specify anything but the name of the file, it will be downloaded to the folder where you run the script. You can put things like "C:\images\Python-Logo.gif" in there if you want it to go somewhere else.&lt;br /&gt;&lt;br /&gt;Using what we have gone over in the first two example, we can write some useful scripts. W can get the page source, find all of the PDF files linked to on it, and download them all to a folder. I do this very thing for one of the classes that I am currently taking. Here is that script:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import urllib&lt;br /&gt;import re&lt;br /&gt;import os&lt;br /&gt;&lt;br /&gt;# Specifiy where I want to download my files&lt;br /&gt;download_folder = "C:\DropBox\My Dropbox\School\CS479\Slides\\"&lt;br /&gt;&lt;br /&gt;# Download page contents &lt;br /&gt;# Note: I am using urllib, not urllib2, but only because urlretrieve does not exist in urllib2&lt;br /&gt;# Both have this method, and do pretty much the same thing (for this kind of usage, that is)&lt;br /&gt;page_contents = urllib.urlopen("https://cswiki.cs.byu.edu/cs479/index.php/Lecture_slides").read()&lt;br /&gt;&lt;br /&gt;# Use regular expression to find all pdf files on site. &lt;br /&gt;# match.group(1) will contain the link to the file&lt;br /&gt;# match.group(2) will contain the name of the file &lt;br /&gt;for match in re.finditer(r'&amp;lt;a href="(.*?\.pdf)"[^&gt;]+&gt;([^&lt;]+)', page_contents):&lt;br /&gt;   file_url = match.group(1)&lt;br /&gt;   # Remove any characters that files cannot contain&lt;br /&gt;   file_name = re.sub(r'(\\|/|:|\*|\?|"|&lt;|&gt;|\|)', "", match.group(2))&lt;br /&gt;   # Check and see if I have already downloaded the file or not&lt;br /&gt;   if not os.path.exists(download_folder + file_name + '.pdf'):&lt;br /&gt;       print "Downloading new file {0}...".format(file_name)&lt;br /&gt;       urllib.urlretrieve(file_url, download_folder + file_name + '.pdf')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Lets make things more interesting - lets say that you want to get the content of a page, but you must be logged in to get at it. You can handle this just fine as well. Lets say that you want to get into your Google Voice account and scrape the page for the ever so important "_rnr_se" value. (I know that &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;I have shown this a few times before&lt;/a&gt;, but many people still wonder how to do this, and it is a good, practical example).&lt;br /&gt;&lt;br /&gt;Here are the steps we need to do to make this happen:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Create an "opener" with the urllib2 module, through which all of our requests will be made. The opener will be created with a &lt;a href="http://docs.python.org/library/urllib2.html#urllib2.HTTPCookieProcessor"&gt;HTTPCookieProcessor&lt;/a&gt; that will handle all the &lt;a href="http://en.wikipedia.org/wiki/HTTP_cookie"&gt;Cookies&lt;/a&gt; from request to request (This allows us to stay "logged in").&lt;/li&gt;&lt;li&gt;Install the opener, so whenever we make a request, our opener that we created will be used (and any Cookie data that has been received by previous requests will be sent along and updated when necessary)&lt;/li&gt;&lt;li&gt;Prepare our login credentials, and URL encode them.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Post them to the login page&lt;/li&gt;&lt;li&gt;Do whatever we need once we are logged in.&lt;/li&gt;&lt;/ol&gt;This might seem like a lot, but it really isn't and it is very simple to do. Here is the script for that:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import urllib, urllib2&lt;br /&gt;import re&lt;br /&gt;from getpass import getpass&lt;br /&gt;&lt;br /&gt;email = raw_input("Enter your Gmail username: ")&lt;br /&gt;password = getpass("Enter your password: ")&lt;br /&gt;&lt;br /&gt;opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(opener)&lt;br /&gt;&lt;br /&gt;# Set up login credentials for Google Accounts&lt;br /&gt;# The 'continue' param redirects us to the Google Voice &lt;br /&gt;# homepage, and gives us necessary cookie info&lt;br /&gt;login_params = urllib.urlencode( {&lt;br /&gt;   'Email' : email,&lt;br /&gt;   'Passwd' : password,&lt;br /&gt;   'continue' : 'https://www.google.com/voice/account/signin'&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;# Perform the login. Cookie info sent back will be saved, so we remain logged in&lt;br /&gt;# for future requests when using the opener.&lt;br /&gt;# Once we log in, we will be redirected to a page that contains the _rnr_se value on it. &lt;br /&gt;gv_home_page_contents = opener.open( 'https://www.google.com/accounts/ServiceLoginAuth',  login_params).read()&lt;br /&gt;&lt;br /&gt;# Go through the home page and grab the value for the hidden&lt;br /&gt;# form field "_rnr_se", which must be included when sending texts and dealing with calls&lt;br /&gt;match = re.search(r"'_rnr_se':\s*'([^']+)'", gv_home_page_contents)&lt;br /&gt;&lt;br /&gt;if not match:&lt;br /&gt;   logged_in = False&lt;br /&gt;else:&lt;br /&gt;   logged_in = True&lt;br /&gt;   _rnr_se = match.group(1)&lt;br /&gt;&lt;br /&gt;if logged_in is True:&lt;br /&gt;   print "Loggin successful! _rnr_se value: {0}".format(_rnr_se)&lt;br /&gt;else:&lt;br /&gt;   print "Loggin was unsuccessful"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you are looking to open up a browser and automatically post form data, look at my other post &lt;a href="http://everydayscripting.blogspot.com/2009/09/python-jquery-open-browser-and-post.html"&gt;here&lt;/a&gt; where I go into more detail.&lt;br /&gt;&lt;br /&gt;Hope this helps!&lt;br /&gt;&lt;br /&gt;If you want to download all of the examples here, here is a &lt;a href="http://sites.google.com/site/everydayscripting/script-source/access_online_content.zip?attredirects=0"&gt;zip-file&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-6480147251342246903?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/6480147251342246903/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/09/python-back-to-basics-accessing-online.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6480147251342246903'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6480147251342246903'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/09/python-back-to-basics-accessing-online.html' title='Python - Back to Basics: Accessing Online Content'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-5762079044357954395</id><published>2009-09-02T06:31:00.000-07:00</published><updated>2010-01-23T12:13:49.076-08:00</updated><title type='text'>Python + Jquery: Open Browser and POST data</title><content type='html'>A &lt;a href="http://everydayscripting.blogspot.com/2009/07/python-opening-urls-in-webbrowser.html"&gt;few entries ago &lt;/a&gt;I talked about how I used Python to run some tests on a web page that I was creating.  Python has a '&lt;a href="http://docs.python.org/library/webbrowser.html"&gt;webbrowser&lt;/a&gt;' module that can open up your default web browser and point it to a specific URL. For the tests that I was running this worked well since the page expected a GET request - all parameters were passed in the URL, which I could change dynamically in my Python source. I didn't need to POST anything to the page with an HTML form.&lt;br /&gt;&lt;br /&gt;I wanted a way to open up pages in my web browser with Python, but perform a POST request, sending data along with it. This functionality is not supported by the webbrowser module, nor do I see how it could. So, I came up with solution that meets my needs. It involves &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; and creating then deleting a temporary file.&lt;br /&gt;&lt;br /&gt;For those of you who do not know, &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; is a JavaScript library that makes programming in JavaScript downright enjoyable, and provides easy solutions to common problems. Google has a &lt;a href="http://code.google.com/apis/ajaxlibs/"&gt;Javascript API&lt;/a&gt; that lets you easily download stable versions of many different JavaScript libraries, including jQuery. You can do this wherever you would like, and makes it so you don't have to keep the source local (there are many benefits to this approach).&lt;br /&gt;&lt;br /&gt;So, these are the steps we take to open a page with Python, and post our predefined (hard-coded or dynamic) form data to a page our our liking:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Dynamically create a complete HTML file.&lt;/li&gt;&lt;li&gt;Include jQuery on the page (this makes it much easier to know when the form is ready for submission).&lt;/li&gt;&lt;li&gt;Create a form on the page with the appropriate action and method.&lt;/li&gt;&lt;li&gt;Insert hidden form elements with their corresponding names and values.&lt;/li&gt;&lt;li&gt;Submit the form when the DOM is finished (jQuery helps with that).&lt;/li&gt;&lt;li&gt;Delete the file when we are done.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;There are lots of ways that you could use this, but I made it as a way to test web pages I am working on. Just as an example, here is a script that you could use to open up a page after having entered your Gmail login credentials:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import os&lt;br /&gt;import webbrowser&lt;br /&gt;&lt;br /&gt;# Set up form elements - these will become the input elements in the form&lt;br /&gt;input_value = {&lt;br /&gt;    'Passwd' : 'YOUR PASSWORD HERE', &lt;br /&gt;    'Email' : 'YOUR USER NAME', &lt;br /&gt;    'continue': 'https://mail.google.com'&lt;br /&gt;    }&lt;br /&gt;action='https://www.google.com/accounts/ServiceLoginAuth?service=mail'&lt;br /&gt;method='post'&lt;br /&gt;&lt;br /&gt;#Set up javascript form submition function. &lt;br /&gt;# I am using the 'format' function on a string, and it doesn't like the { and } of the js function&lt;br /&gt;# so I brought it out to be inserted later&lt;br /&gt;js_submit = '$(document).ready(function() {$("#form").submit(); });'&lt;br /&gt;&lt;br /&gt;# Set up file content elements&lt;br /&gt;input_field = '&amp;lt;input type="hidden" name="{0}" value="{1}" /&amp;gt;'&lt;br /&gt;&lt;br /&gt;base_file_contents = """&lt;br /&gt;&amp;lt;script src='http://www.google.com/jsapi'&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script&amp;gt;&lt;br /&gt;    google.load('jquery', '1.3.2');&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;script&amp;gt;&lt;br /&gt;    {0}&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;form id='form' action='{1}' method='{2}' /&amp;gt;&lt;br /&gt;    {3}&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;&lt;br /&gt;# Build input fields&lt;br /&gt;input_fields = ""&lt;br /&gt;&lt;br /&gt;for key, value in input_value.items():&lt;br /&gt;    input_fields += input_field.format(key, value)&lt;br /&gt;    &lt;br /&gt;# Open web page    &lt;br /&gt;with open('temp_file.html', "w") as file:&lt;br /&gt;    file.write(base_file_contents.format(js_submit,action, method, input_fields))&lt;br /&gt;    file.close()&lt;br /&gt;    webbrowser.open(os.path.abspath(file.name))&lt;br /&gt;    os.remove(os.path.abspath(file.name))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Let me know if you find this useful!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-5762079044357954395?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/5762079044357954395/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/09/python-jquery-open-browser-and-post.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/5762079044357954395'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/5762079044357954395'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/09/python-jquery-open-browser-and-post.html' title='Python + Jquery: Open Browser and POST data'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-1424943496241201663</id><published>2009-09-01T07:59:00.000-07:00</published><updated>2010-01-23T11:51:04.833-08:00</updated><title type='text'>Python - Kronos Workforce Management Clock In/Out</title><content type='html'>I have been working on the Web Team of the  BYU Marriott School for almost a year now. Overall it has been a very enjoyable experience, except for one thing: Kronos. Kronos is an online time management software solution that we must use to keep track of the hours that we work - you clock in when you come in, and you clock out when you leave. Pretty simple problem with a simple solution, but they have complicated this process by providing many unnecessary levels of navigation with unintuitive and inconvenient controls.&lt;br /&gt;&lt;br /&gt;If you have two campus jobs (which I do) then it makes matters worse as the means of clocking in requires additional steps. Even though I only ever clock into one job through this website, there is no way for me to tell the system I want to use this job as my default job. It has hardwired itself to always use this other job as the default - there is no way to change that. I must explicitly tell it every time I clock in that I am clocking if for "Job 2". Also, there is also no way to quickly and easily check if you are clocked in (sometimes I forget if I clocked in). I have to go through 3 levels of navigation to find my time card and check if there is a clock-in time without a clock-out time next to it. The system won't even tell me - I have to determine my status by inspecting my time card personally.&lt;br /&gt;&lt;br /&gt;There are many other shortcomings, but these are my biggest complaints. (I am not just a complainer, I already implemented my own time-tracker/management  system that handles all of these problems in PHP that unfortunately I can't use at work for payroll purposes.)&lt;br /&gt;&lt;br /&gt;So, I wrote a script in Python that will clock in and out of Kronos for me. It isn't too sophisticated, but it works well for my purposes. The script actually does not check to see if you are logged in or out, but based on the name of the file it will perform either the clock in or clock out actions. For example - if the name of the file is "Kronos Login.py" then it will clock you in, giving Kronos the correct job code (you hard-code that in, so I made my clock in job code "Job 2" since that is the only one I ever use). After it has done this, the script will change the name of the file it is in to "Kronos Logout.py". The next time it is run, is sees that it is named "Kronos Logout.py" and will clock out, then change it's name back to "Kronos Login.py". Like I said - not sophisticated, but it solves my problems: No clumsy navigation, and I can just check my Desktop for the script to see my clocked in/out status. I have been using it for a few days now without any problems.&lt;br /&gt;&lt;br /&gt;Here is the source (or &lt;a href="http://sites.google.com/site/everydayscripting/script-source/KronosLogin.py?attredirects=0"&gt;download it directly&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import sys, os&lt;br /&gt;import urllib2, urllib&lt;br /&gt;&lt;br /&gt;# Set up useful variables&lt;br /&gt;file_name = os.path.basename(sys.argv[0])&lt;br /&gt;full_path = os.path.abspath(file_name)&lt;br /&gt;current_directory = os.getcwd() + '\\'&lt;br /&gt;&lt;br /&gt;username = 'YOUR USERNAME'&lt;br /&gt;password = &lt;ENTER PASSWORD OR USE getpass MODULE (ie. getpass.gepass('Enter your password: '))&gt;&lt;br /&gt;&lt;br /&gt;# URLS&lt;br /&gt;kronos_home_page = 'https://kronprod.byu.edu/wfc/applications/suitenav/navigation.do?ESS=true'&lt;br /&gt;kronos_login = 'https://kronprod.byu.edu/wfc/portal'&lt;br /&gt;kronos_timestamp = 'https://kronprod.byu.edu/wfc/applications/wtk/html/ess/timestamp-record.jsp'&lt;br /&gt;&lt;br /&gt;# If you have more than one job, set the number of the job you want to clock into here&lt;br /&gt;# Example: '2' or '1' ('1' Kronos will assume you want job 1 by default, so you don't need to set that one)&lt;br /&gt;# Leave blank if you only have one job. &lt;br /&gt;job_number = '2'&lt;br /&gt;&lt;br /&gt;# Create our opener&lt;br /&gt;opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(opener)&lt;br /&gt;&lt;br /&gt;login_credentials = urllib.urlencode({&lt;br /&gt;    'password': password,&lt;br /&gt;    'username': username&lt;br /&gt;    })&lt;br /&gt;&lt;br /&gt;# Open home page to get cookies &lt;br /&gt;opener.open(kronos_home_page)&lt;br /&gt;&lt;br /&gt;# Login&lt;br /&gt;opener.open(kronos_login, login_credentials)&lt;br /&gt;&lt;br /&gt;#Clock in or clock out&lt;br /&gt;if ('Logout' in file_name):&lt;br /&gt;    transfer = '' # Logging out - transfer parameter must be blank&lt;br /&gt;else:&lt;br /&gt;    if job_number != '':&lt;br /&gt;        transfer = '////Job ' + job_number + '/'&lt;br /&gt;    else:&lt;br /&gt;        transfer = ''&lt;br /&gt;       &lt;br /&gt;time_stamp_parameters = urllib.urlencode({&lt;br /&gt;    'transfer' : transfer&lt;br /&gt;    })&lt;br /&gt;    &lt;br /&gt;opener.open(kronos_timestamp, time_stamp_parameters)&lt;br /&gt;&lt;br /&gt;if ('Login' in file_name):&lt;br /&gt;    os.rename(full_path, current_directory + 'Kronos Logout.py')    &lt;br /&gt;else:&lt;br /&gt;    os.rename(full_path, current_directory + 'Kronos Login.py')    &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I hope to later turn this into a Windows sidebar gadget - if I am successful, I will post that code as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-1424943496241201663?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/1424943496241201663/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/09/python-kronos-workforce-management.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1424943496241201663'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1424943496241201663'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/09/python-kronos-workforce-management.html' title='Python - Kronos Workforce Management Clock In/Out'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-7653921520332832760</id><published>2009-08-20T08:08:00.000-07:00</published><updated>2010-01-23T11:49:41.771-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cookies'/><category scheme='http://www.blogger.com/atom/ns#' term='login'/><category scheme='http://www.blogger.com/atom/ns#' term='_rnr_se'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='google app engine'/><title type='text'>Google App Engine - Cookie Handling with URL Fetch</title><content type='html'>I started working on  creating a web based solution (on &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt;, using the Python API of course) to send mass SMS messages through your Google Voice account this week, but ran into a couple of problems right off the bat during some initial testing. The problem was that I could not log into my Google Account - the response always included a message that my "browser" didn't have cookies enabled. I was confused by this since I was using the &lt;span style="font-weight: bold;"&gt;exact&lt;/span&gt; method and code that I use in my &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;Google Voice Command Line Script&lt;/a&gt;, which works perfectly:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(self.opener)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Doing this will allow you to use the newly created "opener" object to open any URLs, and any Cookie data that is sent from the server is saved and resubmitted in the headers of each additional request. So, if you are visiting a site that requires authorization, you can send your credentials to the login page, and each subsequent request made with that opener will contain the Cookie info. This lets you access the protected areas/privileges of the site (such as sending SMS messages or making calls with your GV account) without much effort at all - it really is a nice feature of the language.&lt;br /&gt;&lt;br /&gt;Well, I set this up in my app, but no luck. So I looked around for a bit and found out that the urllib2, urllib and httplib libraries perform requests using Google's URL fetch service (&lt;a href="http://code.google.com/appengine/docs/python/urlfetch/overview.html#Fetching_URLs_in_Python"&gt;read more here&lt;/a&gt;). This isn't terrible, but the important thing is, is that the "urlfetch" service does NOT handle Cookies, even if you use a HTTPCookieProcessor, or a CookieJar (&lt;a href="http://code.google.com/appengine/docs/python/urlfetch/fetchfunction.html"&gt;read more about that here&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;This entry isn't a &lt;a href="http://en.wikipedia.org/wiki/HTTP_cookie"&gt;lesson on what Cookie's&lt;/a&gt; really are and how they work, but you should know that Cookie information is sent back from the server, and sent to the server in the headers portion of the request/response. Although the urlfetch service does not handle Cookie automatically, it does give you full access to process the header information received from a server, and also what information to send in the headers when making a request to the server. So, I built a separate class that I could use to open up URL's that would handle all Cookie information for me, as well as handle any redirects. The Google Account login system uses redirects when logging in - they forward you to a bunch of different sites to make sure you are actually logged in and that you have the right Cookie info.&lt;br /&gt;&lt;br /&gt;Here is the class:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import urllib, urllib2, Cookie&lt;br /&gt;from google.appengine.api import urlfetch&lt;br /&gt;&lt;br /&gt;class URLOpener:&lt;br /&gt;  def __init__(self):&lt;br /&gt;      self.cookie = Cookie.SimpleCookie()&lt;br /&gt;    &lt;br /&gt;  def open(self, url, data = None):&lt;br /&gt;      if data is None:&lt;br /&gt;          method = urlfetch.GET&lt;br /&gt;      else:&lt;br /&gt;          method = urlfetch.POST&lt;br /&gt;    &lt;br /&gt;      while url is not None:&lt;br /&gt;          response = urlfetch.fetch(url=url,&lt;br /&gt;                          payload=data,&lt;br /&gt;                          method=method,&lt;br /&gt;                          headers=self._getHeaders(self.cookie),&lt;br /&gt;                          allow_truncated=False,&lt;br /&gt;                          follow_redirects=False,&lt;br /&gt;                          deadline=10&lt;br /&gt;                          )&lt;br /&gt;          data = None # Next request will be a get, so no need to send the data again. &lt;br /&gt;          method = urlfetch.GET&lt;br /&gt;          self.cookie.load(response.headers.get('set-cookie', '')) # Load the cookies from the response&lt;br /&gt;          url = response.headers.get('location')&lt;br /&gt;    &lt;br /&gt;      return response&lt;br /&gt;        &lt;br /&gt;  def _getHeaders(self, cookie):&lt;br /&gt;      headers = {&lt;br /&gt;                 'Host' : 'www.google.com',&lt;br /&gt;                 'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)',&lt;br /&gt;                 'Cookie' : self._makeCookieHeader(cookie)&lt;br /&gt;                  }&lt;br /&gt;      return headers&lt;br /&gt;&lt;br /&gt;  def _makeCookieHeader(self, cookie):&lt;br /&gt;      cookieHeader = ""&lt;br /&gt;      for value in cookie.values():&lt;br /&gt;          cookieHeader += "%s=%s; " % (value.key, value.value)&lt;br /&gt;      return cookieHeader&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The class is simple, but works for my purposes. The class really has only one method that you should worry about: "open". Lets walk through that method: First, it checks to see if you are posting any data, and if not it makes it a GET request. Then, it starts into it's main loop. The first loop sends the data (if there is any) and some basic header information in the request, then saves the Cookie info it received in the response, then changes the request method to GET. It checks the headers for the "location" value to see if it needs to be redirected - if it does, it keeps going, saving and sending the received Cookie information along the way. Once it is done, it returns the response of the final location. Once you return the response, you can access the content by calling ".content" on the returned value.&lt;br /&gt;&lt;br /&gt;Currently, it doesn't support GET requests with data, but mainly because I didn't need that for this project. It would be trivial to implement, however.&lt;br /&gt;&lt;br /&gt;Here is an example on how to log in to your Google Voice account, then parse out the ever-so-important "_rnr_se" value using the URLOpener class:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;opener = URLOpener()&lt;br /&gt;&lt;br /&gt;loginParams = urllib.urlencode({&lt;br /&gt;   'Email' : email,&lt;br /&gt;   'Passwd' : password,&lt;br /&gt;   'continue' : 'https://www.google.com/voice/account/signin'&lt;br /&gt;})&lt;br /&gt;      &lt;br /&gt;opener.open( 'https://www.google.com/accounts/ServiceLoginAuth',  loginParams)&lt;br /&gt;&lt;br /&gt;googleVoiceHomePage= opener.open('https://www.google.com/voice/#inbox').content&lt;br /&gt;&lt;br /&gt;match = re.search('name="_rnr_se".*?value="(.*?)"', googleVoiceHomePage)&lt;br /&gt;&lt;br /&gt;_rnr_se = match.group(1)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hope this helps!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-7653921520332832760?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/7653921520332832760/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/google-app-engine-cookie-handling-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/7653921520332832760'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/7653921520332832760'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/google-app-engine-cookie-handling-with.html' title='Google App Engine - Cookie Handling with URL Fetch'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-3440275827343443092</id><published>2009-08-17T20:50:00.000-07:00</published><updated>2010-01-23T10:14:49.519-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='decorators'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Python - Boggle Solver</title><content type='html'>I have been a long time fan of the game &lt;a href="http://en.wikipedia.org/wiki/Boggle"&gt;Boggle&lt;/a&gt;. Last year in one of my CS classes we were required to create a program in Java that would find all the words on any given Boggle board - a very fun project, and one that I found useful for a number of reasons. The other day I was messing around with my old Java implementation, and thought it would be good practice to convert it to Python. During the process, I learned a few new tricks about Python and also some other solutions to problems that may come up in other places.&lt;br /&gt;&lt;br /&gt;The first useful thing I learned how to do was to create a class that acted as if it had two __init__ functions, which of course Python cannot handle. There are &lt;a href="http://stackoverflow.com/questions/682504/what-is-a-clean-pythonic-way-to-have-multiple-constructors-in-python"&gt;a couple of ways to do it&lt;/a&gt;, but my favorite implementation uses the "&lt;a href="http://docs.python.org/library/functions.html#classmethod"&gt;@classmethod&lt;/a&gt;" decorator. The "@classmethod" decorator makes the method static (not like in Java or C++ - use &lt;a href="http://docs.python.org/library/functions.html#staticmethod"&gt;@staticmethod&lt;/a&gt; for that) - so within the method you can initialize a new object, set the instance variables then return the newly created object once you are done. Here is a code snippet from my Word class where I used this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;class Word:&lt;br /&gt;    def __init__(self):&lt;br /&gt;        self.letter_sequence = ""&lt;br /&gt;        self.used_board_coordinates = set()&lt;br /&gt;&lt;br /&gt;    @classmethod&lt;br /&gt;    def new(cls, row, column):&lt;br /&gt;        word = cls()&lt;br /&gt;        word.used_board_coordinates.add((row, column))&lt;br /&gt;        return word&lt;br /&gt;&lt;br /&gt;    @classmethod&lt;br /&gt;    def new_from_word(cls, word):&lt;br /&gt;        new_word = cls()&lt;br /&gt;        new_word.letter_sequence += word.letter_sequence&lt;br /&gt;        new_word.used_board_coordinates.update(word.used_board_coordinates)&lt;br /&gt;        return new_word&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;One of the constructors (called "new") accepts two parameters - the originating coordinates. It is called like so:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;word = Word.new(row, column)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The second takes one parameter - another word. It creates a new word, and then copies over the data it needs. You call it much like the first: &lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;wordCopy = Word.new_from_word(word)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Another problem I had to solve was prefix look-up. I have a large dictionary, and while searching for words on the board I only wanted to pursue directions that could potentially lead to actual words - so I needed to be able to &lt;span style="font-weight: bold;"&gt;quickly&lt;/span&gt; see if a letter sequence is a valid word prefix. I considered implementing a &lt;a href="http://en.wikipedia.org/wiki/Trie"&gt;Trie&lt;/a&gt; or something similar, or using a modified &lt;a href="http://en.wikipedia.org/wiki/Binary_Search"&gt;Binary Search&lt;/a&gt; like I did in my Java program. I didn't really like either solution, so I came up with a solution that can take up a lot of memory, but runs extremely quickly - a simple set(). For each word loaded from my dictionary, I run a for loop that adds incrementing amounts of the word into my prefix set.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;for index in xrange(len(word.strip()) + 1):&lt;br /&gt;    self.prefixes.add(word[:index]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So, when I receive the word "python" from my dictionary, I add "p", "py", "pyt", "pyth"...etc to my prefix set. I was first afraid at how long it would take to do this, since the dictionary I have has 170,000+ words in it. It takes less than 1 second, and the look-up times are top-notch.&lt;br /&gt;&lt;br /&gt;I also found a use for a &lt;a href="http://everydayscripting.blogspot.com/2009/07/python-generators-examples-and.html"&gt;generator&lt;/a&gt; - it could have been done without it, but I like how it cleans up the code. I use one in the BoggleSolver class - it helps me loop through all of the valid moves (coordinates) for a given word.  The rules are this for Boggle - you can only use each letter (coordinate) once when constructing a word, and you can only move to adjacent spaces that are on the board. Here an example from my code:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;for row, column in self._get_valid_coodinates_for_word(word, row, column):&lt;br /&gt;    if(self.dictionary.contains_prefix(word.letter_sequence + self.board[row][column])):&lt;br /&gt;        self._find_words(Word.new_from_word(word), row, column)&lt;br /&gt;&lt;br /&gt;def _get_valid_coodinates_for_word(self, word, row, column):&lt;br /&gt;    for r in range(row - 1, row + 2):&lt;br /&gt;        for c in range(column - 1, column + 2):&lt;br /&gt;            if r &gt;= 0 and r &lt; self.board.side_length and c &gt;= 0 and c &lt; self.board.side_length:&lt;br /&gt;                if ((r, c) not in word.used_board_coordinates):&lt;br /&gt;                    yield r, c&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;There are a lot of "for"s and "if" statements there, I know - but I thought it made it more readable, and it doesn't affect the performance. So, to get the coordinates for the word, simply pass the word and the current coordinates to the _get_valid_coordinates_for_word generator, and you can iterate over the returned row, column values. Pretty slick, if you ask me.&lt;br /&gt;&lt;br /&gt;The actual problem that this script solves may not be too interesting to others, but some of the problems it solves within itself might, so I am posting the source. If you want to use it, it is ready to be used at the command line. Type "python pyBoggle.py" then a space separated list of the letters on your board. The board must be a square - 3x3, 4x4, 5x5...etc for it to word (it checks).&lt;br /&gt;&lt;br /&gt;Example:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;python pyBoggle.py a d e g n u p t e m l p w e f t&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can download the source &lt;a href="http://sites.google.com/site/everydayscripting/script-source/pyBoggle.py?attredirects=0"&gt;here&lt;/a&gt;.&lt;br /&gt;Be sure to grab the dictionary &lt;a href="http://sites.google.com/site/everydayscripting/script-source/dictionary.txt?attredirects=0"&gt;here&lt;/a&gt; (Place it in the same folder as the script).&lt;br /&gt;&lt;br /&gt;Here is the source if you wish to just view it:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;class BoggleSolver:&lt;br /&gt;    def __init__(self, letter_list):&lt;br /&gt;        self.dictionary = Dictionary("C:\\Dropbox\\Programming Projects\\Python\\PyBoggle\\dictionary.txt")&lt;br /&gt;        self.board = Board(letter_list)&lt;br /&gt;        self.min_length = 4&lt;br /&gt;        self.found_words = set()&lt;br /&gt;&lt;br /&gt;        # Find all words starting from each coordinate position&lt;br /&gt;        for row in xrange(self.board.side_length):&lt;br /&gt;            for column in xrange(self.board.side_length):&lt;br /&gt;                self._find_words(Word.new(row, column), row, column)&lt;br /&gt;&lt;br /&gt;    def _find_words(self, word, row, column):&lt;br /&gt;        word.add_letter(self.board[row][column], row, column)&lt;br /&gt;&lt;br /&gt;        if (self._can_add_word(word)):&lt;br /&gt;            self.found_words.add(word.letter_sequence)&lt;br /&gt;&lt;br /&gt;        for row, column in self._get_valid_coodinates_for_word(word, row, column):&lt;br /&gt;            if(self.dictionary.contains_prefix(word.letter_sequence + self.board[row][column])):&lt;br /&gt;                self._find_words(Word.new_from_word(word), row, column)&lt;br /&gt;&lt;br /&gt;    def _can_add_word(self, word):&lt;br /&gt;        return len(word) &gt;= self.min_length and self.dictionary.contains_word(word.letter_sequence)&lt;br /&gt;&lt;br /&gt;    def _get_valid_coodinates_for_word(self, word, row, column):&lt;br /&gt;        for r in range(row - 1, row + 2):&lt;br /&gt;            for c in range(column - 1, column + 2):&lt;br /&gt;                if r &gt;= 0 and r &lt; self.board.side_length and c &gt;= 0 and c &lt; self.board.side_length:&lt;br /&gt;                    if ((r, c) not in word.used_board_coordinates):&lt;br /&gt;                        yield r, c&lt;br /&gt;&lt;br /&gt;class Board:&lt;br /&gt;    def __init__(self, letter_list):&lt;br /&gt;        self.side_length = len(letter_list) ** .5&lt;br /&gt;        if (self.side_length != int(self.side_length)):&lt;br /&gt;            raise Exception("Board must have equal sides! (4x4, 5x5...)")&lt;br /&gt;        else:&lt;br /&gt;            self.side_length = int(self.side_length)&lt;br /&gt;&lt;br /&gt;        self.board = []&lt;br /&gt;&lt;br /&gt;        index = 0&lt;br /&gt;        for row in xrange(self.side_length):&lt;br /&gt;            self.board.append([])&lt;br /&gt;            for column in xrange(self.side_length):&lt;br /&gt;                self.board[row].append(letter_list[index])&lt;br /&gt;                index += 1&lt;br /&gt;&lt;br /&gt;    def __getitem__(self, row):&lt;br /&gt;        return self.board[row]&lt;br /&gt;&lt;br /&gt;class Word:&lt;br /&gt;    def __init__(self):&lt;br /&gt;        self.letter_sequence = ""&lt;br /&gt;        self.used_board_coordinates = set()&lt;br /&gt;&lt;br /&gt;    @classmethod&lt;br /&gt;    def new(cls, row, column):&lt;br /&gt;        word = cls()&lt;br /&gt;        word.used_board_coordinates.add((row, column))&lt;br /&gt;        return word&lt;br /&gt;&lt;br /&gt;    @classmethod&lt;br /&gt;    def new_from_word(cls, word):&lt;br /&gt;        new_word = cls()&lt;br /&gt;        new_word.letter_sequence += word.letter_sequence&lt;br /&gt;        new_word.used_board_coordinates.update(word.used_board_coordinates)&lt;br /&gt;        return new_word&lt;br /&gt;&lt;br /&gt;    def add_letter(self, letter, row, column):&lt;br /&gt;        self.letter_sequence += letter&lt;br /&gt;        self.used_board_coordinates.add((row, column))&lt;br /&gt;&lt;br /&gt;    def __str__(self):&lt;br /&gt;        return self.letter_sequence&lt;br /&gt;&lt;br /&gt;    def __len__(self):&lt;br /&gt;        return len(self.letter_sequence)&lt;br /&gt;&lt;br /&gt;class Dictionary:&lt;br /&gt;    def __init__(self, dictionary_file):&lt;br /&gt;        self.words = set()&lt;br /&gt;        self.prefixes = set()&lt;br /&gt;        word_file = open(dictionary_file, "r")&lt;br /&gt;&lt;br /&gt;        for word in word_file.readlines():&lt;br /&gt;            self.words.add(word.strip())&lt;br /&gt;            for index in xrange(len(word.strip()) + 1):&lt;br /&gt;                self.prefixes.add(word[:index])&lt;br /&gt;&lt;br /&gt;    def contains_word(self, word):&lt;br /&gt;        return word in self.words&lt;br /&gt;&lt;br /&gt;    def contains_prefix(self, prefix):&lt;br /&gt;        return prefix in self.prefixes&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    boggleSolver = BoggleSolver(sys.argv[1:])&lt;br /&gt;    words = boggleSolver.found_words &lt;br /&gt;    print words&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Enjoy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-3440275827343443092?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/3440275827343443092/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/python-boggle-solver.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/3440275827343443092'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/3440275827343443092'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/python-boggle-solver.html' title='Python - Boggle Solver'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-6732526270625543463</id><published>2009-08-08T09:11:00.000-07:00</published><updated>2011-01-25T19:47:50.539-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='interactive'/><category scheme='http://www.blogger.com/atom/ns#' term='calling'/><category scheme='http://www.blogger.com/atom/ns#' term='google voice'/><category scheme='http://www.blogger.com/atom/ns#' term='command line'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='sms'/><title type='text'>Python + Google Voice. Mass SMS and Iterative Calling at the Command Line</title><content type='html'>&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="line-height: 16px; "&gt;&lt;span class="Apple-style-span"&gt;&lt;b&gt;EDIT (Jan 2011): Google changed the way contacts are downloaded. Many of the scripts below will break. &lt;a href="http://everydayscripting.blogspot.com/2011/01/gvoicepy-updated.html"&gt;Use the updated version here.&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="color: rgb(153, 0, 0); font-weight: bold; "&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 0); "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="font-weight: bold; "&gt;&lt;span class="Apple-style-span"&gt;EDIT (Oct 3 2009): Google changed a few things on their login page which broke my old scripts. I have updated the scripts in this post, including those that you can download. If you have been receiving "Could not log in with provided credentials" errors, these new scripts should fix that problem.&lt;/span&gt;&lt;/div&gt;&lt;div style="font-weight: bold; "&gt;&lt;br /&gt;&lt;/div&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 153); font-style: italic; font-weight: bold; "&gt; You can download a ready-to-use-no-Python-installation-required Windows executable version of the program &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold; font-style: italic;"&gt;&lt;span style="color: rgb(255, 102, 0);"&gt;&lt;a href="http://sites.google.com/site/everydayscripting/executables/GVMassContact.zip?attredirects=0"&gt;here&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="color: rgb(255, 102, 0);"&gt;&lt;a style="color: rgb(0, 0, 153); font-style: italic;" href="http://sites.google.com/site/everydayscripting/executables/GVMassContact.zip?attredirects=0"&gt;.&lt;/a&gt;&lt;span style="color: rgb(0, 0, 153); font-style: italic;"&gt; Just download, unzip, find the GVMassContact.exe file and double click to use. This version's source is identical to those of scripts located below. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 102, 0);"&gt; Click &lt;/span&gt;&lt;a style="color: rgb(255, 102, 0);" href="http://www.blogger.com/post-edit.g?blogID=5484413222728812890&amp;amp;postID=6732526270625543463#downloads"&gt;here&lt;/a&gt;&lt;span style="color: rgb(255, 102, 0);"&gt; to navigate to the downloadable Python scripts.&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I have already written twice about Google Voice (&lt;a href="http://everydayscripting.blogspot.com/2009/07/python-google-voice-revisited.html"&gt;here&lt;/a&gt; and &lt;a href="http://everydayscripting.blogspot.com/2009/07/google-voice-python-sms.html"&gt;here&lt;/a&gt;), but the scripts in this installment are an improvement over both of the scripts provided the other posts.&lt;br /&gt;&lt;br /&gt;I wanted to create an interactive command line script that would allow me to either send an SMS message or call everyone in one of my Google Contacts Groups. Google Voice does not yet let you send mass text messages, or call people in an iterative fashion,  so I came up with a script to let me do just that.&lt;br /&gt;&lt;br /&gt;Instead of using Googles &lt;a href="http://code.google.com/apis/gdata/"&gt;gdata Python API&lt;/a&gt; to access my Google Contacts, I decided  to use Pythons &lt;a href="http://docs.python.org/library/csv.html"&gt;"csv" module&lt;/a&gt; to parse an exported csv file downloaded from my GMail account. Doing this lets you run the script without needing to download any other libraries, assuming you are using a version of Python that has all the features used in this script (I use &lt;a href="http://www.python.org/download/releases/2.6.2/"&gt;2.6.2&lt;/a&gt; currently).&lt;br /&gt;&lt;br /&gt;The first thing I did was create a "&lt;span style="font-weight: bold;"&gt;gvoice.py&lt;/span&gt;" module with several helpful classes. These allow you to:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Log in&lt;/li&gt;&lt;li&gt;Gather all Google Contacts into separate groups&lt;/li&gt;&lt;li&gt;Selectively narrow down the contacts in a group&lt;/li&gt;&lt;li&gt;Gather the phone numbers that you have entered &lt;/li&gt;&lt;li&gt;Send SMS messages&lt;/li&gt;&lt;li&gt;Place Calls&lt;/li&gt;&lt;/ol&gt;I am by no means a seasoned Python developer, but what I have created works well for my purposes. I tried to make the classes as loosely coupled with the UI as possible in case I want to put a GUI around it sometime, but for now I am doing everything at the command line (which I often prefer).&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-weight: bold;"&gt;GoogleVoiceLogin&lt;/span&gt; class will allow you to log in, get the &lt;a href="http://docs.python.org/library/urllib2.html"&gt;"opener"&lt;/a&gt; which lets you keep your log in cookie data for subsequent requests, and gives you access to the _rnr_se value (the "key") that is used when sending SMS messages or placing a call. Most of the other classes in the "gvoice" module will accept the opener and key as parameters to their constructors.&lt;br /&gt;&lt;br /&gt;I created a sample script that uses the classes in my "gvoice" module to:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Prompt the user for his/her Google Account credentials&lt;/li&gt;&lt;li&gt;Allow the user to select a Group from his/her Google Contacts Groups.&lt;/li&gt;&lt;li&gt;Allow the user to narrow down the contact list of the Group (nothing permanent is done to the Contacts)&lt;/li&gt;&lt;li&gt;Choose whether to send an SMS message or call everyone in the list&lt;/li&gt;&lt;li&gt;If calling, it allows you to choose a number from your phone list, or enter a custom number (like the Google Voice site does when placing calls)&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;The script that I wrote assumes I always want to use the contact's mobile phone (the first one) to send SMS messages and place calls, but that is easy to change.&lt;br /&gt;&lt;br /&gt;There are two files needed for this to work - here is the main driver program (gvMassContact.py):&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;from gvoice import *&lt;br /&gt;import getpass&lt;br /&gt;import sys&lt;br /&gt;import re&lt;br /&gt;import os&lt;br /&gt;&lt;br /&gt;# Function used to create a separator&lt;br /&gt;def separator():&lt;br /&gt;return '-' * 25&lt;br /&gt;&lt;br /&gt;def get_numeric_input(prompt):&lt;br /&gt;try:&lt;br /&gt;return int(raw_input(prompt))&lt;br /&gt;except:&lt;br /&gt;pass&lt;br /&gt;&lt;br /&gt;# Function to clear the screen&lt;br /&gt;def clear_screen():&lt;br /&gt;if os.name == "posix":&lt;br /&gt;# *nix systems&lt;br /&gt;os.system('clear')&lt;br /&gt;elif os.name in ("nt", "dos", "ce"):&lt;br /&gt;# Windows&lt;br /&gt;os.system('CLS')&lt;br /&gt;&lt;br /&gt;# Main method to be run &lt;br /&gt;def main():&lt;br /&gt;# Log in&lt;br /&gt;print "Please enter your Google Account credentials"&lt;br /&gt;email = raw_input("User name: ")&lt;br /&gt;password = getpass.getpass("Password: ")&lt;br /&gt;&lt;br /&gt;gv = GoogleVoiceLogin(email, password)&lt;br /&gt;if not gv.logged_in:&lt;br /&gt;print "Could not log in with provided credentials"&lt;br /&gt;sys.exit(1)&lt;br /&gt;else:&lt;br /&gt;print "Login successful!"&lt;br /&gt;&lt;br /&gt;# Use the ContactLoader to download Google Contacts &lt;br /&gt;contact_loader = ContactLoader(gv.opener)&lt;br /&gt;&lt;br /&gt;# Use the ContactSelector to select the group and&lt;br /&gt;# final list of contacts to contact&lt;br /&gt;contact_selector = ContactSelector(contact_loader.contacts_by_group_list)&lt;br /&gt;&lt;br /&gt;clear_screen()&lt;br /&gt;group_list = contact_selector.get_group_list()&lt;br /&gt;selected_group = None&lt;br /&gt;while selected_group not in range(1, len(group_list)+1):&lt;br /&gt;print "Your Google Groups"&lt;br /&gt;print separator()&lt;br /&gt;for group_item in group_list:&lt;br /&gt;   print "{0}: {1}".format(group_item[0], group_item[1])&lt;br /&gt;print separator()&lt;br /&gt;selected_group = get_numeric_input("Enter the index of the group to select: ")&lt;br /&gt;&lt;br /&gt;clear_screen()&lt;br /&gt;# Now that a group is selected, narrow down the list of people in the group&lt;br /&gt;contact_selector.set_selected_group(selected_group)&lt;br /&gt;removing = True&lt;br /&gt;while removing:&lt;br /&gt;print "Contact List"&lt;br /&gt;print separator()&lt;br /&gt;for contact_item in contact_selector.get_contacts_list():&lt;br /&gt;   print "{0}: {1}".format(contact_item[0], contact_item[1])&lt;br /&gt;print separator()&lt;br /&gt;try:&lt;br /&gt;   input_list = raw_input("Enter a list of the indexes (coma, space or otherwise delimeted)\nof those contacts you do not wish to contact this session.\nPress enter when finished:  ")&lt;br /&gt;   if input_list != '':&lt;br /&gt;       contacts_to_remove_list  = [int(match.group(1)) for match in re.finditer(r"(\d+)", input_list)]&lt;br /&gt;       contact_selector.remove_from_contact_list(contacts_to_remove_list)&lt;br /&gt;   else:&lt;br /&gt;       removing = False&lt;br /&gt;except:&lt;br /&gt;   pass&lt;br /&gt;&lt;br /&gt;clear_screen()&lt;br /&gt;# Print final list&lt;br /&gt;clear_screen()&lt;br /&gt;print "Final List:"&lt;br /&gt;print separator()&lt;br /&gt;for contact_item in contact_selector.get_contacts_list():&lt;br /&gt;print "{0}".format(contact_item[1])&lt;br /&gt;selected_option = None&lt;br /&gt;while selected_option not in [1, 2]:&lt;br /&gt;print separator()&lt;br /&gt;print "Options: "&lt;br /&gt;print "1: Send Text"&lt;br /&gt;print "2: Call"&lt;br /&gt;print separator()&lt;br /&gt;selected_option = get_numeric_input("Select which action to take: ")&lt;br /&gt;&lt;br /&gt;# Send texts to all people in contact list&lt;br /&gt;if (selected_option == 1):&lt;br /&gt;print separator()&lt;br /&gt;text_sender = TextSender(gv.opener, gv.key)&lt;br /&gt;text = raw_input("Enter text message. Press enter when finished: ")&lt;br /&gt;text_sender.text = text&lt;br /&gt;for contact in contact_selector.get_contacts_list():&lt;br /&gt;   number = contact[1].mobile&lt;br /&gt;   if number == '':&lt;br /&gt;       print "{0} does not have a mobile number".format(contact[1])&lt;br /&gt;   else:&lt;br /&gt;       print "Sending message to {0} at {1}...".format(contact[1], contact[1].mobile),&lt;br /&gt;       text_sender.send_text(contact[1].mobile)&lt;br /&gt;       if text_sender.response:&lt;br /&gt;           print "Success!"&lt;br /&gt;       else:&lt;br /&gt;           print "Failed!!"&lt;br /&gt;  &lt;br /&gt;# Call all people in contact list             &lt;br /&gt;elif (selected_option == 2):&lt;br /&gt;print separator()&lt;br /&gt;number_dialer = NumberDialer(gv.opener, gv.key)&lt;br /&gt;&lt;br /&gt;number_retriever = NumberRetriever(gv.opener)&lt;br /&gt;phone_number_items = number_retriever.get_phone_numbers()&lt;br /&gt;&lt;br /&gt;clear_screen()&lt;br /&gt;# Get the forwarding number&lt;br /&gt;forwarding_number_input = None&lt;br /&gt;while forwarding_number_input not in range(1, len(phone_number_items) + 2):&lt;br /&gt;   print "Select forwarding number"&lt;br /&gt;   print separator()&lt;br /&gt;   for phone_number_item in phone_number_items:&lt;br /&gt;       print "{0}: {1}".format(phone_number_item[0], phone_number_item[1][0])&lt;br /&gt;   print "{0}: {1}".format(len(phone_number_items) + 1, "Other")&lt;br /&gt;   print separator()&lt;br /&gt;   forwarding_number_input = get_numeric_input("Choose from your previously entered numbers, or select  \"Other\": ")&lt;br /&gt;&lt;br /&gt;if forwarding_number_input in range(1, len(phone_number_items) + 1):&lt;br /&gt;   forwarding_number = phone_number_items[forwarding_number_input - 1][1]&lt;br /&gt;else:&lt;br /&gt;   forwarding_number = ''&lt;br /&gt;   while not re.match(r"\(?\b[0-9]{3}\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}\b\Z", forwarding_number):&lt;br /&gt;       forwarding_number = raw_input("Enter the forwarding number to dial: ")&lt;br /&gt;number_dialer.forwarding_number = forwarding_number&lt;br /&gt;&lt;br /&gt;print separator()&lt;br /&gt;# Loop through and make the calls&lt;br /&gt;for contact in contact_selector.get_contacts_list():&lt;br /&gt;   number = contact[1].mobile&lt;br /&gt;   if number == '':&lt;br /&gt;       print "{0} does not have a mobile number".format(contact[1])&lt;br /&gt;   else:&lt;br /&gt;       input = None&lt;br /&gt;       while input not in ['', 'n','N', 'q', 'Q'] :&lt;br /&gt;           input = raw_input("Press enter to call {0} at {1} ('n' to skip, 'q' to quit): ".format(contact[1], contact[1].mobile))&lt;br /&gt;       if input == '':&lt;br /&gt;           print "Calling {0}....".format(contact[1]),&lt;br /&gt;           number_dialer.place_call(number)&lt;br /&gt;           if number_dialer.response:&lt;br /&gt;               print "Success!"&lt;br /&gt;           else:&lt;br /&gt;               print "Failed!!"&lt;br /&gt;       elif input .upper() == 'N':&lt;br /&gt;           pass&lt;br /&gt;       elif input.upper() == 'Q':&lt;br /&gt;           print "Call chain aborted."&lt;br /&gt;           break&lt;br /&gt;          &lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;main()&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And here is the other required script (must be named "gvoice.py"):&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import csv&lt;br /&gt;import sys&lt;br /&gt;import re&lt;br /&gt;import urllib&lt;br /&gt;import urllib2&lt;br /&gt;&lt;br /&gt;class GoogleVoiceLogin:&lt;br /&gt;def __init__(self, email, password):&lt;br /&gt;# Set up our opener&lt;br /&gt;self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(self.opener)&lt;br /&gt;&lt;br /&gt;# Define URLs&lt;br /&gt;self.loing_page_url = 'https://www.google.com/accounts/ServiceLogin'&lt;br /&gt;self.authenticate_url = 'https://www.google.com/accounts/ServiceLoginAuth'&lt;br /&gt;self.gv_home_page_url = 'https://www.google.com/voice/#inbox'&lt;br /&gt;&lt;br /&gt;# Load sign in page&lt;br /&gt;login_page_contents = self.opener.open(self.loing_page_url).read()&lt;br /&gt;&lt;br /&gt;# Find GALX value&lt;br /&gt;galx_match_obj = re.search(r'name="GALX"\s*value="([^"]+)"', login_page_contents, re.IGNORECASE)&lt;br /&gt;&lt;br /&gt;galx_value = galx_match_obj.group(1) if galx_match_obj.group(1) is not None else ''&lt;br /&gt;&lt;br /&gt;# Set up login credentials&lt;br /&gt;login_params = urllib.urlencode( {&lt;br /&gt;   'Email' : email,&lt;br /&gt;   'Passwd' : password,&lt;br /&gt;   'continue' : 'https://www.google.com/voice/account/signin',&lt;br /&gt;   'GALX': galx_value&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;# Login&lt;br /&gt;self.opener.open(self.authenticate_url, login_params)&lt;br /&gt;&lt;br /&gt;# Open GV home page&lt;br /&gt;gv_home_page_contents = self.opener.open(self.gv_home_page_url).read()&lt;br /&gt;&lt;br /&gt;# Fine _rnr_se value&lt;br /&gt;key = re.search('name="_rnr_se".*?value="(.*?)"', gv_home_page_contents)&lt;br /&gt;&lt;br /&gt;if not key:&lt;br /&gt;   self.logged_in = False&lt;br /&gt;else:&lt;br /&gt;   self.logged_in = True&lt;br /&gt;   self.key = key.group(1)&lt;br /&gt;&lt;br /&gt;class ContactLoader():&lt;br /&gt;def __init__(self, opener):&lt;br /&gt;self.opener = opener&lt;br /&gt;self.contacts_csv_url = "http://mail.google.com/mail/contacts/data/export"&lt;br /&gt;self.contacts_csv_url += "?groupToExport=^Mine&amp;amp;exportType=ALL&amp;amp;out=OUTLOOK_CSV"&lt;br /&gt;&lt;br /&gt;# Load ALL Google Contacts into csv dictionary&lt;br /&gt;self.contacts = csv.DictReader(self.opener.open(self.contacts_csv_url))&lt;br /&gt;&lt;br /&gt;# Create dictionary to store contacts and groups in an easier format&lt;br /&gt;self.contact_group = {}&lt;br /&gt;# Assigned each person to a group that we can get at later&lt;br /&gt;for row in self.contacts:&lt;br /&gt;   if row['First Name'] != '':&lt;br /&gt;       for category in row['Categories'].split(';'):&lt;br /&gt;           if category == '':&lt;br /&gt;               category  = 'Ungrouped'&lt;br /&gt;           if category not in self.contact_group:&lt;br /&gt;               self.contact_group[category] = [Contact(row)]&lt;br /&gt;           else:&lt;br /&gt;               self.contact_group[category].append(Contact(row))&lt;br /&gt;&lt;br /&gt;# Load contacts into a list of tuples...&lt;br /&gt;# [(1, ('group_name', [contact_list])), (2, ('group_name', [contact_list]))]&lt;br /&gt;self.contacts_by_group_list = [(id  + 1, group_contact_item)&lt;br /&gt;                              for id, group_contact_item in enumerate(self.contact_group.items())]&lt;br /&gt;&lt;br /&gt;class Contact():&lt;br /&gt;def __init__(self,contact_detail):&lt;br /&gt;self.first_name = contact_detail['First Name'].strip()&lt;br /&gt;self.last_name = contact_detail['Last Name'].strip()&lt;br /&gt;self.mobile = contact_detail['Mobile Phone'].strip()&lt;br /&gt;self.email = contact_detail['E-mail Address'].strip()&lt;br /&gt;&lt;br /&gt;def __str__(self):&lt;br /&gt;return self.first_name + ' ' + self.last_name&lt;br /&gt;&lt;br /&gt;# Class to assist in selected contacts by groups&lt;br /&gt;class ContactSelector():&lt;br /&gt;def __init__(self, contacts_by_group_list):&lt;br /&gt;self.contacts_by_group_list = contacts_by_group_list&lt;br /&gt;self.contact_list = None&lt;br /&gt;&lt;br /&gt;def get_group_list(self):&lt;br /&gt;return [(item[0], item[1][0]) for item in self.contacts_by_group_list]&lt;br /&gt;&lt;br /&gt;def set_selected_group(self, group_id):&lt;br /&gt;self.contact_list  = self.contacts_by_group_list[group_id - 1][1][1]&lt;br /&gt;&lt;br /&gt;# Return the contact list so far&lt;br /&gt;def get_contacts_list(self):&lt;br /&gt;return [(id + 1, contact) for id, contact in enumerate(self.contact_list)]&lt;br /&gt;&lt;br /&gt;# Accept a list of indexes to remove from the current contact list&lt;br /&gt;# Assumes 1 based list being passed in&lt;br /&gt;def remove_from_contact_list(self, contacts_to_remove_list):&lt;br /&gt;if self.contact_list == None:&lt;br /&gt;   return&lt;br /&gt;for id in contacts_to_remove_list:&lt;br /&gt;   if id in range(0, len(self.contact_list)+1):&lt;br /&gt;       self.contact_list[id - 1] = None&lt;br /&gt;self.contact_list = [contact for contact in self.contact_list if contact is not None]&lt;br /&gt;&lt;br /&gt;class NumberRetriever():&lt;br /&gt;def __init__(self, opener):&lt;br /&gt;self.opener = opener&lt;br /&gt;self.phone_numbers_url = 'https://www.google.com/voice/settings/tab/phones'&lt;br /&gt;phone_numbers_page_content = self.opener.open(self.phone_numbers_url).read()&lt;br /&gt;&lt;br /&gt;# Build list of all numbers and their aliases&lt;br /&gt;self.phone_number_items = [(match.group(1), match.group(2))&lt;br /&gt;                          for match&lt;br /&gt;                          in re.finditer('"name":"([^"]+)","phoneNumber":"([^"]+)"',&lt;br /&gt;                                                   phone_numbers_page_content)]&lt;br /&gt;&lt;br /&gt;def get_phone_numbers(self):&lt;br /&gt;return [(id + 1, (phone_number_item))&lt;br /&gt;       for id, phone_number_item&lt;br /&gt;       in enumerate(self.phone_number_items)]&lt;br /&gt;&lt;br /&gt;class TextSender():&lt;br /&gt;def __init__(self, opener, key):&lt;br /&gt;self.opener = opener&lt;br /&gt;self.key = key&lt;br /&gt;self.sms_url = 'https://www.google.com/voice/sms/send/'&lt;br /&gt;self.text = ''&lt;br /&gt;&lt;br /&gt;def send_text(self, phone_number):&lt;br /&gt;sms_params = urllib.urlencode({&lt;br /&gt;   '_rnr_se': self.key,&lt;br /&gt;   'phoneNumber': phone_number,&lt;br /&gt;   'text': self.text&lt;br /&gt;})&lt;br /&gt;# Send the text, display status message&lt;br /&gt;self.response  = self.opener.open(self.sms_url, sms_params).read()&lt;br /&gt;&lt;br /&gt;class NumberDialer():&lt;br /&gt;def __init__(self, opener, key):&lt;br /&gt;self.opener = opener&lt;br /&gt;self.key = key&lt;br /&gt;self.call_url = 'https://www.google.com/voice/call/connect/'&lt;br /&gt;self.forwarding_number = None&lt;br /&gt;&lt;br /&gt;def place_call(self, number):&lt;br /&gt;call_params = urllib.urlencode({&lt;br /&gt;   'outgoingNumber' : number,&lt;br /&gt;   'forwarding_number' : self.forwarding_number,&lt;br /&gt;   'subscriberNumber' : 'undefined',&lt;br /&gt;   'remember' : '0',&lt;br /&gt;   '_rnr_se': self.key&lt;br /&gt;   })&lt;br /&gt;&lt;br /&gt;# Send the text, display status message&lt;br /&gt;self.response  = self.opener.open(self.call_url, call_params).read()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For the "&lt;span style="font-weight: bold;"&gt;gvMassContact.py&lt;/span&gt;" script to work, you need to have the "&lt;span style="font-weight: bold;"&gt;gvoice.py&lt;/span&gt;" file located in the same folder.&lt;br /&gt;&lt;a name="downloads"&gt;&lt;/a&gt;&lt;br /&gt;If you would rather not copy and paste, you can download the files here:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://sites.google.com/site/everydayscripting/script-source/gvMassContact.py?attredirects=0"&gt;gvMassContact.py&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://sites.google.com/site/everydayscripting/script-source/gvoice.py?attredirects=0"&gt;gvoice.py&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;If you don't care about having the files separated, and would rather have to deal with just one script that you can keep on your desktop, you can download this file:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://sites.google.com/site/everydayscripting/script-source/gvAllInOne.py?attredirects=0"&gt;gvAllInOne.py&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold; color: rgb(0, 0, 153);"&gt;Here is a new, ready-to-use-no-Python-installation-required &lt;a href="http://sites.google.com/site/everydayscripting/executables/GVMassContact.zip?attredirects=0"&gt;Windows executable &lt;/a&gt;version. Just download, unzip, find GVMassContact.exe in the GVMassContact folder, double click and go.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The only difference between the "&lt;span style="font-weight: bold;"&gt;allInOne.py&lt;/span&gt;" script and the others is that all of the classes contained in the "&lt;span style="font-weight: bold;"&gt;gvoice.py&lt;/span&gt;" file are located at the top of the file.&lt;br /&gt;&lt;br /&gt;If you download the "&lt;span style="font-weight: bold;"&gt;gvAllInOne.py&lt;/span&gt;" script, you can keep it on your desktop and simply double click it to get the interactive command line session to come up.&lt;br /&gt;&lt;br /&gt;Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-6732526270625543463?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/6732526270625543463/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html#comment-form' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6732526270625543463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6732526270625543463'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html' title='Python + Google Voice. Mass SMS and Iterative Calling at the Command Line'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-1594961027022660329</id><published>2009-08-08T08:08:00.001-07:00</published><updated>2009-08-08T18:51:31.501-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='command line'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='environment'/><category scheme='http://www.blogger.com/atom/ns#' term='path'/><title type='text'>Easier Python script execution in Widows</title><content type='html'>While this post does not contain a sample script as all previous posts do, it can make creating and executing scripts easier if you are working on a Windows machine. There are two things that I like to set up to accomplish this:&lt;br /&gt;&lt;br /&gt;1) Make widows execute scripts at the command line without needing to type "python" before the name of the script, or the letters ".py" after the name of the script.&lt;br /&gt;2) Put the folder that contains all of my commonly used scripts on the system Path.&lt;br /&gt;&lt;br /&gt;To accomplish the first step, do this:&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Control Panel -&gt; System -&gt; Advanced System Settings -&gt; Environments Variables&lt;/span&gt;&lt;br /&gt;In the new window that shows up, scroll down in the bottom list to find &lt;span style="font-weight: bold;"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;PATHEXT&lt;/span&gt;&lt;/span&gt;. Double click on it and add "&lt;span style="font-weight: bold;"&gt;;.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;PY&lt;/span&gt;&lt;/span&gt;" to the end.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_7au8LV-2iSE/Sn2XwlrAYqI/AAAAAAAAAl0/E5tO7v7iFAE/s1600-h/TextVariables.PNG"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 380px; height: 400px;" src="http://2.bp.blogspot.com/_7au8LV-2iSE/Sn2XwlrAYqI/AAAAAAAAAl0/E5tO7v7iFAE/s400/TextVariables.PNG" alt="" id="BLOGGER_PHOTO_ID_5367613191897506466" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Once you are done with that, double click on the "&lt;span style="font-weight: bold;"&gt;Path&lt;/span&gt;". Append ";[FULL PATH TO YOUR SCRIPT FOLDER]" to what is already there.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_7au8LV-2iSE/Sn2h3ww1rmI/AAAAAAAAAl8/QaBNrYF3yN4/s1600-h/Path.PNG"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 400px; height: 386px;" src="http://4.bp.blogspot.com/_7au8LV-2iSE/Sn2h3ww1rmI/AAAAAAAAAl8/QaBNrYF3yN4/s400/Path.PNG" alt="" id="BLOGGER_PHOTO_ID_5367624310250122850" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;full path="" your="" script="" folder="" to="" the="" end="" of="" what="" is="" already=""&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Done!&lt;br /&gt;&lt;br /&gt;If you have a script in your folder that you just added to your Path called "&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;helloWorld&lt;/span&gt;.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;py&lt;/span&gt;" you can now simply type "&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;helloWorld&lt;/span&gt;" and it will be run. It isn't that much better than "python &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;helloWorld&lt;/span&gt;.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;py&lt;/span&gt;", but I prefer this method.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/full&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-1594961027022660329?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/1594961027022660329/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/easier-python-script-execution-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1594961027022660329'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1594961027022660329'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/easier-python-script-execution-in.html' title='Easier Python script execution in Widows'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_7au8LV-2iSE/Sn2XwlrAYqI/AAAAAAAAAl0/E5tO7v7iFAE/s72-c/TextVariables.PNG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-4744626830364613398</id><published>2009-08-05T06:17:00.000-07:00</published><updated>2010-01-23T10:52:49.582-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='drag and drop'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='zip'/><title type='text'>Python - Drag and Drop Zip</title><content type='html'>I often need to quickly zip a folder or file on my desktop to email off to someone. I have &lt;a href="http://www.7-zip.org/"&gt;7-zip&lt;/a&gt; installed, and it is a great program, but wanted to come up with something even easier to use as well as try something new.&lt;br /&gt;&lt;br /&gt;I created a Python script that sits on my desktop called "Zip". To create a zip archive of a file or folder, I simply drag and drop the file onto the "Zip" script and a new .zip file will show up wherever the script is located. It works just fine, and does not require any non-standard Python modules. Tested on on Windows, Python 2.6.2.&lt;br /&gt;&lt;br /&gt;Here is the script source:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import os, sys&lt;br /&gt;import zipfile&lt;br /&gt;&lt;br /&gt;# Make sure a folder or directory was specified&lt;br /&gt;if len(sys.argv) == 1:&lt;br /&gt;   sys.exit(1)&lt;br /&gt;&lt;br /&gt;# Assign name of file or folder to resusable variable&lt;br /&gt;resource = sys.argv[1]   &lt;br /&gt;&lt;br /&gt;# Create name of new zip file, based on original folder or file name    &lt;br /&gt;zipFileName = os.path.splitext(resource)[0] + '.zip'&lt;br /&gt;&lt;br /&gt;# Create zip file&lt;br /&gt;zipFile = zipfile.ZipFile(zipFileName, "w")&lt;br /&gt;  &lt;br /&gt;# Function to create the archive name &lt;br /&gt;# Otherwise the zip folder contains many, unnecessary sub folders    &lt;br /&gt;def getArchiveName(resource, root, file):&lt;br /&gt;   if root == resource:&lt;br /&gt;       return (os.sep).join([resource, file])&lt;br /&gt;   else:&lt;br /&gt;       return (os.sep).join([resource, root, file])&lt;br /&gt;      &lt;br /&gt;# Write file(s) to the zipFile        &lt;br /&gt;print "Creating zip archive..."&lt;br /&gt;if os.path.isdir(resource):&lt;br /&gt;   for root, dirs, files in os.walk(resource):&lt;br /&gt;       for file in files:&lt;br /&gt;           zipFile.write((os.sep).join([root, file]),&lt;br /&gt;               getArchiveName(os.path.basename(resource), os.path.basename(root), file),&lt;br /&gt;               zipfile.ZIP_DEFLATED)&lt;br /&gt;else:&lt;br /&gt;   zipFile.write(resource, os.path.basename(resource), zipfile.ZIP_DEFLATED)&lt;br /&gt;&lt;br /&gt;# Close file when finished    &lt;br /&gt;zipFile.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can also download the script here: &lt;a href="http://sites.google.com/site/everydayscripting/script-source/Zip.py"&gt;Zip.py&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The unzip version is coming soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-4744626830364613398?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/4744626830364613398/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/python-drag-and-drop-zip.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/4744626830364613398'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/4744626830364613398'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/08/python-drag-and-drop-zip.html' title='Python - Drag and Drop Zip'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-4694571822479833329</id><published>2009-07-28T14:43:00.000-07:00</published><updated>2010-01-23T10:48:12.449-08:00</updated><title type='text'>Python - Opening URLs in a webbrowser</title><content type='html'>The other day at work I needed to test a web page that I had created. The page was a form that would allow certain logged in users to suggest changes to profile information that we have on a group of people. The form data was pulled from a database, and the page knew whose information to pull based on a URL parameter called "mem" - which contained the numeric primary key of the directory member's record in the database. There were several places where the page could crash, based on what was coming our of the database, as the original creator didn't conform to a  standard way of representing a "NULL" value.  Some fields held NULL, some 0, some an empty string - and some columns could be any of those 3, but all meant the same thing. To test the page, I would simply change the "mem" parameter to pull up someone else's data - this would help me find any other bugs that I may have overlooked. The page would crash, but in our testing environment the error message (detailing the error and the line number where it occurred) is embedded within the page. I wanted to be completely thorough with this testing, but there are several hundred members of this directory, and trying every single possibility by hand would have taken a VERY long time.&lt;br /&gt;&lt;br /&gt;Setting up a proper testing environment with unit tests and everything would be ideal - but where I work that sadly isn't standard practice, and we don't have a good way of doing that. So, I improvised by writing a quick script in Python to test every single possibility for me. First, I grabbed a list of all the primary keys of the members in the database, and then set up a URL opener using a HTTPCookieProcessor, so I could get into the protected page. I looped through the list, assigning the "mem" parameter to the primary keys in the list, and checked for a HTTPError,  which would happen only if the page crashed (HTTPError is thrown when there is a 501 Server Error).&lt;br /&gt;&lt;br /&gt;Since there was a server error, the URL opener wouldn't let me download the page to process the error message - so I used the Python webbrowser module to simply open the page up in Firefox for me - that way I could look at the page myself and examine the error. Since the page I was testing is in a protected area of the site, I needed to first open up Firefox and log in - Python can't pass cookie data to Firefox. After that, all I needed to do was run the script. All the pages that had problems would open as new tabs in my currently running Firefox instance.&lt;br /&gt;&lt;br /&gt;Here is the script (obviously changed since I can't be giving out login credentials):&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import urllib2, urllib&lt;br /&gt;import webbrowser&lt;br /&gt;testMemberIds = [&lt;LIST OF NUMERIC PRIMARY KEYS&gt;]&lt;br /&gt;&lt;br /&gt;opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(opener)&lt;br /&gt;&lt;br /&gt;loginParams = urllib.urlencode( {&lt;br /&gt;'login' : '&lt;USERNAME&gt;',&lt;br /&gt;'password' : '&lt;PASSWORD&gt;',&lt;br /&gt;} )&lt;br /&gt;opener.open( 'http://&lt;LOGIN URL&gt;',  loginParams)&lt;br /&gt;&lt;br /&gt;for memNum in testMemberIds:&lt;br /&gt;   print "Trying {0}...".format(memNum),&lt;br /&gt;   try:&lt;br /&gt;       opener.open("&lt;TEST PAGE URL&gt;?mem={0}".format(memNum))&lt;br /&gt;   except urllib2.HTTPError, e:&lt;br /&gt;       print "Server error! - Member: {0}".format(memNum)&lt;br /&gt;       webbrowser.open("&lt;TEST PAGE URL&gt;?mem={0}".format(memNum))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is a pretty hackish way of testing a site, but it got the job done for me. It would have taken hours to test every possible primary key in the list by hand, but this script only took a a few minutes to throw together, and I found all the errors that could possibly come up (with the data currently in the database that is) and fixed - all in less than 20 minutes.&lt;br /&gt;&lt;br /&gt;This is the first time I used the webbrowser module to do anything truly helpful to me, but it got me thinking about some other uses for it - possibly opening up separate tabs  for items that match certain criteria on eBay or CraigsList. Just a thought - might be something to play with later on.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-4694571822479833329?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/4694571822479833329/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/python-opening-urls-in-webbrowser.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/4694571822479833329'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/4694571822479833329'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/python-opening-urls-in-webbrowser.html' title='Python - Opening URLs in a webbrowser'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-6254920437709335098</id><published>2009-07-27T21:05:00.000-07:00</published><updated>2010-01-23T10:44:24.094-08:00</updated><title type='text'>Python Generators - examples and applications</title><content type='html'>Python generators are very handy - albeit a little hard to understand if you have never worked with them before. It took me a while to find out what it is they actually do, and even longer to figure out a use for one.&lt;br /&gt;&lt;br /&gt;If you don't know what a generator is, or what one can do, this is the definition I came up with while learning about them: A generator is like an extended function that remembers that state it was in the last time it was called, and will continue from there using that same state. Generators look a lot like regular functions, but they have that characteristic "yield" keyword that sets them apart from conventional functions.&lt;br /&gt;&lt;br /&gt;Here is a simple example:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# Generator example&lt;br /&gt;def printName(name):&lt;br /&gt;    for section in name.split(' '):&lt;br /&gt;        yield section&lt;br /&gt;&lt;br /&gt;for section in printName("Guido van Rossum"):&lt;br /&gt;    print section&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;All this generator does is it takes a name and splits it into the different sections that are separated by spaces. It's output will look like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_7au8LV-2iSE/Sm6B2cyo6zI/AAAAAAAAAEk/m1ibN-8xphM/s1600-h/generator1.PNG"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 400px; height: 110px;" src="http://2.bp.blogspot.com/_7au8LV-2iSE/Sm6B2cyo6zI/AAAAAAAAAEk/m1ibN-8xphM/s400/generator1.PNG" alt="" id="BLOGGER_PHOTO_ID_5363366978686348082" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In this example, we are treating the generator like an iterator - which is a very common usage of generators, but definitely not the only usage.&lt;br /&gt;&lt;br /&gt;Another way we can use a generator is by assigning it to another variable - then, every time we want the next value "yielded" by  the generator, we call "&amp;lt;variablename&gt;.next()".&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;def getNextWordGenerator():&lt;br /&gt;    yield "Hello"&lt;br /&gt;    yield "this"&lt;br /&gt;    yield "is"&lt;br /&gt;    yield "an"&lt;br /&gt;    yield "example"&lt;br /&gt;&lt;br /&gt;generator = getNextWordGenerator()&lt;br /&gt;&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Running this will give us:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_7au8LV-2iSE/Sm6JH6hjI0I/AAAAAAAAAEs/E8FkPVGa_B8/s1600-h/generator2.png"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 400px; height: 121px;" src="http://3.bp.blogspot.com/_7au8LV-2iSE/Sm6JH6hjI0I/AAAAAAAAAEs/E8FkPVGa_B8/s400/generator2.png" alt="" id="BLOGGER_PHOTO_ID_5363374975306900290" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;But, if we try to call "generator.next()" in this example, we will get an error. This is because there is nothing left in the generator to yield. This may be what you want to happen, but maybe not. Sometimes you would rather have it start all over again. In that case, you can just put all of your yield statements in a "while True:" loop, like so:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;def getNextWordGenerator():&lt;br /&gt;    while True:&lt;br /&gt;        yield "Hello"&lt;br /&gt;        yield "this"&lt;br /&gt;        yield "is"&lt;br /&gt;        yield "an"&lt;br /&gt;        yield "example"&lt;br /&gt;&lt;br /&gt;generator = getNextWordGenerator()&lt;br /&gt;&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;print&lt;br /&gt;print 'Second time around, but only a little'&lt;br /&gt;print generator.next()&lt;br /&gt;print generator.next()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The output this time around will look like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_7au8LV-2iSE/Sm6KtOh8_lI/AAAAAAAAAE0/O7svXf6pP2U/s1600-h/generator3.PNG"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 400px; height: 130px;" src="http://1.bp.blogspot.com/_7au8LV-2iSE/Sm6KtOh8_lI/AAAAAAAAAE0/O7svXf6pP2U/s400/generator3.PNG" alt="" id="BLOGGER_PHOTO_ID_5363376715844091474" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;This is just the beginning, however - you can do more complicated and useful things (of course, that depends on your definition of useful).&lt;br /&gt;&lt;br /&gt;Lets says, for example, you are a member of &lt;a href="http://projecteuler.net/"&gt;Project Euler&lt;/a&gt; and you need a function that will spit out Fibonacci numbers. There are a few ways of calculating Fibonacci numbers, but I think that Python generators is the easiest, and fastest way. Here is an example:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;def fib():&lt;br /&gt;    x,y = 1,1&lt;br /&gt;    while True:&lt;br /&gt;        yield x&lt;br /&gt;        x,y = y, x+y&lt;br /&gt;&lt;br /&gt;for num in fib():&lt;br /&gt;    print num&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Output:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_7au8LV-2iSE/Sm6QfPHaTAI/AAAAAAAAAE8/Ygh1Jon8EDQ/s1600-h/generator4.PNG"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 320px; height: 400px;" src="http://3.bp.blogspot.com/_7au8LV-2iSE/Sm6QfPHaTAI/AAAAAAAAAE8/Ygh1Jon8EDQ/s400/generator4.PNG" alt="" id="BLOGGER_PHOTO_ID_5363383072552799234" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Running this will print out Fibonacci numbers at an alarming rate. You will have to hit CTRL-C to kill the script, or it will keep on going forever - and fast.  I felt...odd...putting a "while True:" loop in my code for this first time when doing this. That seemed like something I should avoid. Don't worry though - it is not going to peg your processor or anything - the loop will only be run when called. It will not be run in he background without you knowing about it.&lt;br /&gt;&lt;br /&gt;This example also acts as a reminder that a generator can remember it's state. In this case, the generator keeps track of the fact that it is in a loop, and it remembers the values in "x" and "y". So, the next time fib().next() is called by the iterator (this all happens automatically in the for loop) it &lt;span style="font-weight: bold;"&gt;doesn't&lt;/span&gt; start over at the top of the generator with "x,y = 1,1", but in the loop where it last left off. Very handy.&lt;br /&gt;&lt;br /&gt;So, how can this help in every day scripting situations? Well, I am not too sure. I have found them very useful in solutions to Project Euler problems, and I used a generator in a school project to return all valid adjacent points to a point that I passed into the generator - but other than that, I haven't found a great reason to use them frequently in every day situations. But, there are some libraries and other built in functions of Python that do use them heavily. For example - xrange uses a "generator" rather than creating a list first in memory like "range"&lt;span style="font-weight: bold;"&gt;&lt;/span&gt; does. "count" in the "itertools" uses a generator to give you the next number in sequence for as long as you want.&lt;br /&gt;&lt;br /&gt;You can use generators to simulate &lt;a href="http://en.wikipedia.org/wiki/Continuation"&gt;continuation programming&lt;/a&gt; - which also takes some time getting used to, but is pretty neat when you see it. Here is an example of that:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;def fib():&lt;br /&gt;  x,y = 1,1&lt;br /&gt;  while True:&lt;br /&gt;      yield x&lt;br /&gt;      x,y = y, x+y&lt;br /&gt;&lt;br /&gt;def odd(seq):&lt;br /&gt;  for number in seq:&lt;br /&gt;      if number % 2:&lt;br /&gt;          yield number&lt;br /&gt; &lt;br /&gt;def underFourMillion(seq):&lt;br /&gt;  for number in seq:&lt;br /&gt;      if number &gt; 4000000:&lt;br /&gt;          break&lt;br /&gt;      yield number &lt;br /&gt;         &lt;br /&gt;print sum(odd(underFourMillion(fib())))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This program sums all of the odd Fibonacci numbers that are under 4 million, but at no point does it store anything in a data structure. The "sum" built-in method will add numbers coming from the "odd" generator, which yields any odd numbers coming from the "underFourMillion" generator, which yields any number that is under 4 million coming from the "fib" generator. Neat. It is easy to change any part of this program, or even add another "filter" generator to the mix.&lt;br /&gt;&lt;br /&gt;Do you have any other uses for generators? Share them in the comments.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-6254920437709335098?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/6254920437709335098/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/python-generators-examples-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6254920437709335098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/6254920437709335098'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/python-generators-examples-and.html' title='Python Generators - examples and applications'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_7au8LV-2iSE/Sm6B2cyo6zI/AAAAAAAAAEk/m1ibN-8xphM/s72-c/generator1.PNG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-7476345662804214658</id><published>2009-07-21T15:03:00.000-07:00</published><updated>2010-01-23T10:37:19.486-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='google voide'/><category scheme='http://www.blogger.com/atom/ns#' term='sys'/><category scheme='http://www.blogger.com/atom/ns#' term='regular expressions'/><category scheme='http://www.blogger.com/atom/ns#' term='google'/><title type='text'>Redirect Python output</title><content type='html'>&lt;myprogramname&gt;&lt;span style="font-weight: bold;"&gt;EDIT: Updated scripts to use the new login system described &lt;/span&gt;&lt;a style="font-weight: bold;" href="http://everydayscripting.blogspot.com/2009/10/python-fixes-to-google-login-script.html"&gt;here.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Today at work I was working on a script to create some reports for one of my superiors. During my testing I was outputting everything to the screen so I could view it quickly and easily. My boss needed me to send him a file containing the data I was generating, and I didn't look forward to changing all of my print statements (&lt;/myprogramname&gt;my script had many, many print statements)&lt;myprogramname&gt; to something like "file.write(whateverwasbeingprintedbefore)". I could have done this, of course (and it could have been done in one easy step with the &lt;/myprogramname&gt;he&lt;myprogramname&gt;lp of &lt;a href="http://www.regexbuddy.com/"&gt;RegexBuddy&lt;/a&gt;&lt;/myprogramname&gt;) but that still seemed too inflexible of a solution. Python has a very simple solution for this problem, and it just takes an extra two lines of code.&lt;br /&gt;&lt;br /&gt;My work example would have been boring here, so I thought up something else useful. Using my &lt;a href="http://everydayscripting.blogspot.com/2009/07/python-google-voice-revisited.html"&gt;Google Voice Login&lt;/a&gt; class, I created another script to print out whatever is in my SMS inbox (up to the first 10) in a semi decent format. I used a couple of regular expressions, which I could not have created without RegexBuddy, to parse the page for the information I wanted. Here is that script (which requires the &lt;a href="http://sites.google.com/site/everydayscripting/script-source/gvoice.py?attredirects=0"&gt;gvoice.py&lt;/a&gt; module I wrote):&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# Print SMS inbox&lt;br /&gt;from gvoice import GoogleVoiceLogin&lt;br /&gt;import urllib&lt;br /&gt;import re&lt;br /&gt;import getpass&lt;br /&gt;&lt;br /&gt;# Create an instance of a GoogleVoiceLogin object&lt;br /&gt;# This will prompt you for your Google Account credentials&lt;br /&gt;email = raw_input('Enter your Google Username: ')&lt;br /&gt;password = getpass.getpass('Enter your password: ')&lt;br /&gt;&lt;br /&gt;gv_login = GoogleVoiceLogin(email, password)&lt;br /&gt;&lt;br /&gt;# Create our regular expressions (Created in RegexBuddy)&lt;br /&gt;# Regular Expression to gather information on each SMS Conversation&lt;br /&gt;sms_conversation_regex = re.compile(&lt;br /&gt;   r"""&amp;lt;span\sclass="gc-message-name"&amp;gt;\s*         # Get the message conversation info div&lt;br /&gt;    &amp;lt;span\sclass=""&amp;gt;([^&amp;lt;]*?)&amp;lt;/span&amp;gt;\s*          # Get who originated the conversation&lt;br /&gt;    &amp;lt;span.*?&amp;lt;/span&amp;gt;.*?                          # Eat this div - we don't need it&lt;br /&gt;    .*?&amp;gt;([^&amp;lt;]*).*?                              # Get who the conversation was started with&lt;br /&gt;    &amp;lt;div\sclass="gc-message-message-display"&amp;gt;\s*&lt;br /&gt;    (&amp;lt;div\sclass="gc-message-sms-row"&amp;gt;.*?       # Get all the messages of the conversation&lt;br /&gt;    &amp;lt;/div&amp;gt;\s*)*                                 # Close up our divs&lt;br /&gt;    &amp;lt;/div&amp;gt;""",&lt;br /&gt;   re.DOTALL | re.VERBOSE)&lt;br /&gt;  &lt;br /&gt;# Regular expression to gather information on individual messages within an SMS conversaion&lt;br /&gt;sms_details_regex = re.compile(&lt;br /&gt;   r"""&amp;lt;div\sclass="gc-message-sms-row"&amp;gt;.*?         # Get up the message div&lt;br /&gt;    &amp;lt;span\sclass="gc-message-sms-from"&amp;gt;\s*(.*?)\s*   # Get who the message was from&lt;br /&gt;    &amp;lt;/span&amp;gt;.*?&lt;br /&gt;    &amp;lt;span\sclass="gc-message-sms-text"&amp;gt;\s*([^&amp;lt;]*)\s*.*?&amp;lt;/div&amp;gt; # Get the message data""",&lt;br /&gt;   re.DOTALL | re.VERBOSE)&lt;br /&gt;&lt;br /&gt;# Get the open (Cookie data still intact)&lt;br /&gt;opener =gv_login.opener&lt;br /&gt;&lt;br /&gt;sms_inbox_content = opener.open("https://www.google.com/voice/inbox/recent/sms/").read()&lt;br /&gt;&lt;br /&gt;# Nested for-loops of regular expressions - not the best way, but the easiest for now. &lt;br /&gt;for sms_conversation_match in sms_conversation_regex.finditer(sms_inbox_content):&lt;br /&gt;   print&lt;br /&gt;   print "---{0} to {1} {2}".format(sms_conversation_match.group(1), sms_conversation_match.group(2), '-' * 50)&lt;br /&gt;   for message_details_match in sms_details_regex.finditer(sms_conversation_match.group()):&lt;br /&gt;        print "{0}: {1}".format(message_details_match.group(1), message_details_match.group(2))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Running this script will show you the first 10 SMS conversations in your inbox, if there are any. But, it is limited right now to displaying the information on the screen. Using these next two lines, you can put all of this information in a file of your choice:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;sys.stdout = open("SMS Conversations.txt", "w")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;To get this to work in the SMS inbox script, I insert the "import sys" command right below the other imports. The "sys.stdout" line goes right below the line where I instantiate the GoogleVoiceLogin object. I deliberately do that, otherwise I won't be able to see the prompt, because it will be written to the file!&lt;br /&gt;&lt;br /&gt;This is a very easy way to change the output of your program - I hope you find it as useful as I did.&lt;br /&gt;&lt;br /&gt;Here is the final script:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# Print SMS inbox&lt;br /&gt;from gvoice import GoogleVoiceLogin&lt;br /&gt;import urllib&lt;br /&gt;import re&lt;br /&gt;import sys&lt;br /&gt;import getpass&lt;br /&gt;&lt;br /&gt;# Create an instance of a GoogleVoiceLogin object&lt;br /&gt;# This will prompt you for your Google Account credentials&lt;br /&gt;email = raw_input('Enter your Google Username: ')&lt;br /&gt;password = getpass.getpass('Enter your password: ')&lt;br /&gt;&lt;br /&gt;gv_login = GoogleVoiceLogin(email, password)&lt;br /&gt;&lt;br /&gt;sys.stdout = open("SMS Conversations.txt", "w")&lt;br /&gt;&lt;br /&gt;# Create our regular expressions (Created in RegexBuddy)&lt;br /&gt;# Regular Expression to gather information on each SMS Conversation&lt;br /&gt;sms_conversation_regex = re.compile(&lt;br /&gt;   r"""&amp;lt;span\sclass="gc-message-name"&amp;gt;\s*         # Get the message conversation info div&lt;br /&gt;    &amp;lt;span\sclass=""&amp;gt;([^&amp;lt;]*?)&amp;lt;/span&amp;gt;\s*          # Get who originated the conversation&lt;br /&gt;    &amp;lt;span.*?&amp;lt;/span&amp;gt;.*?                          # Eat this div - we don't need it&lt;br /&gt;    .*?&amp;gt;([^&amp;lt;]*).*?                              # Get who the conversation was started with&lt;br /&gt;    &amp;lt;div\sclass="gc-message-message-display"&amp;gt;\s*&lt;br /&gt;    (&amp;lt;div\sclass="gc-message-sms-row"&amp;gt;.*?       # Get all the messages of the conversation&lt;br /&gt;    &amp;lt;/div&amp;gt;\s*)*                                 # Close up our divs&lt;br /&gt;    &amp;lt;/div&amp;gt;""",&lt;br /&gt;   re.DOTALL | re.VERBOSE)&lt;br /&gt;  &lt;br /&gt;# Regular expression to gather information on individual messages within an SMS conversaion&lt;br /&gt;sms_details_regex = re.compile(&lt;br /&gt;   r"""&amp;lt;div\sclass="gc-message-sms-row"&amp;gt;.*?         # Get up the message div&lt;br /&gt;    &amp;lt;span\sclass="gc-message-sms-from"&amp;gt;\s*(.*?)\s*   # Get who the message was from&lt;br /&gt;    &amp;lt;/span&amp;gt;.*?&lt;br /&gt;    &amp;lt;span\sclass="gc-message-sms-text"&amp;gt;\s*([^&amp;lt;]*)\s*.*?&amp;lt;/div&amp;gt; # Get the message data""",&lt;br /&gt;   re.DOTALL | re.VERBOSE)&lt;br /&gt;&lt;br /&gt;# Get the open (Cookie data still intact)&lt;br /&gt;opener =gv_login.opener&lt;br /&gt;&lt;br /&gt;sms_inbox_content = opener.open("https://www.google.com/voice/inbox/recent/sms/").read()&lt;br /&gt;&lt;br /&gt;# Nested for-loops of regular expressions - not the best way, but the easiest for now. &lt;br /&gt;for sms_conversation_match in sms_conversation_regex.finditer(sms_inbox_content):&lt;br /&gt;   print&lt;br /&gt;   print "---{0} to {1} {2}".format(sms_conversation_match.group(1), sms_conversation_match.group(2), '-' * 50)&lt;br /&gt;   for message_details_match in sms_details_regex.finditer(sms_conversation_match.group()):&lt;br /&gt;        print "{0}: {1}".format(message_details_match.group(1), message_details_match.group(2))&lt;br /&gt;   print "---{0} to {1} {2}".format(sms_conversation_match.group(1), sms_conversation_match.group(2), '-' * 50)&lt;br /&gt;   for message_details_match in sms_details_regex.finditer(sms_conversation_match.group()):&lt;br /&gt;        print "{0}: {1}".format(message_details_match.group(1), message_details_match.group(2))&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-7476345662804214658?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/7476345662804214658/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/redirect-python-output.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/7476345662804214658'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/7476345662804214658'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/redirect-python-output.html' title='Redirect Python output'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-2654832898112871694</id><published>2009-07-21T14:38:00.000-07:00</published><updated>2010-01-23T10:28:06.053-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='google voice'/><category scheme='http://www.blogger.com/atom/ns#' term='command line'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='google'/><title type='text'>Python - Google Voice part 2</title><content type='html'>&lt;span style="font-weight: bold; color: rgb(153, 0, 0);font-size:130%;" &gt;EDIT: Google recently made some behind the scenes changes to the login page, which broke this script. &lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(153, 0, 0);font-size:130%;" &gt;Please see the new and improved script &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;here&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;My &lt;a href="http://everydayscripting.blogspot.com/2009/07/google-voice-python-sms.html"&gt;last post &lt;/a&gt;was also about accessing your Google Voice account through Python, but there is more that you can do with it than just send SMS messages. You can make calls, cancel calls, view your call/sms/voicemail history and a few more others, as pointed out by this blogger &lt;a href="http://posttopic.com/topic/google-voice-add-on-development"&gt;here&lt;/a&gt; while talking about his Firefox plugin.&lt;br /&gt;&lt;br /&gt;Since all of these options require you to be logged in, I thought that it would be easier to create a separate class that would:&lt;br /&gt;1) Log you in and let you know whether or not your attempt was successful&lt;br /&gt;2) Provide a method to get the "opener" (which is what keeps cookie data in order during multiple requests),&lt;br /&gt;3) Provide a method to get your "_rnr_se" value, which is required when sending SMS messages, making calls, and canceling calls.&lt;br /&gt;&lt;br /&gt;Instead of hardcoding in Google Account credentials like in my last post, the new class will prompt you for your Google Account user name and password. Since I want to use this script in public, I used the &lt;a href="http://docs.python.org/library/getpass.html"&gt;getpass module &lt;/a&gt;to hide the input as I type it. Here is the script:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# Get URL handling support&lt;br /&gt;import urllib2, urllib&lt;br /&gt;# Get regular expression support&lt;br /&gt;import re, getpass&lt;br /&gt;&lt;br /&gt;class GoogleVoiceLogin:&lt;br /&gt;def __init__(self):&lt;br /&gt;print "Please enter your Google Account credentials"&lt;br /&gt;self.email = raw_input("User name: ")&lt;br /&gt;self.password = getpass.getpass("Password: ")&lt;br /&gt;&lt;br /&gt;# Set up an opener with HTTPCookieProcessor&lt;br /&gt;self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(self.opener)&lt;br /&gt;&lt;br /&gt;# Set up login credentials for Google Accounts&lt;br /&gt;# The 'continue' param redirects us to the Google Voice &lt;br /&gt;# homepage, and gives us necessary cookie info&lt;br /&gt;loginParams = urllib.urlencode( {&lt;br /&gt;    'Email' : self.email,&lt;br /&gt;    'Passwd' : self.password,&lt;br /&gt;    'continue' : 'https://www.google.com/voice/account/signin'&lt;br /&gt;} )&lt;br /&gt;&lt;br /&gt;# Perform the login. Cookie info sent back will be saved, so we remain logged in&lt;br /&gt;# for future requests when using the opener&lt;br /&gt;self.opener.open( 'https://www.google.com/accounts/ServiceLoginAuth',  loginParams)&lt;br /&gt;&lt;br /&gt;# Need to load the homepage to find user specific data&lt;br /&gt;googleVoiceHomeData = self.opener.open('https://www.google.com/voice/#inbox').read()&lt;br /&gt;&lt;br /&gt;# Go through the home page and grab the value for the hidden&lt;br /&gt;# form field "_rnr_se", which must be included when sending texts and dealing with calls&lt;br /&gt;match = re.search('name="_rnr_se".*?value="(.*?)"', googleVoiceHomeData)&lt;br /&gt;if not match:&lt;br /&gt;    print "Login Unsuccessful!"&lt;br /&gt;    exit()&lt;br /&gt;else:&lt;br /&gt;    print "Login Successful!"&lt;br /&gt;    self._rnr_se = match.group(1)&lt;br /&gt;&lt;br /&gt;def getOpener(self):&lt;br /&gt;return self.opener&lt;br /&gt;&lt;br /&gt;def getRnrSe(self):&lt;br /&gt;return self._rnr_se&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The script, for the most part, should be self-explanatory. It pretty much follows the same pattern as the script in my &lt;a href="http://everydayscripting.blogspot.com/2009/07/google-voice-python-sms.html"&gt;previous post&lt;/a&gt; except it is more reusable. For example, here is the new script I use to send text messages from the command line:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# Send a text Message&lt;br /&gt;from GoogleVoiceLogin import GoogleVoiceLogin&lt;br /&gt;import urllib&lt;br /&gt;&lt;br /&gt;# Create an instance of a GoogleVoiceLogin object&lt;br /&gt;# This will prompt you for your Google Account credentials&lt;br /&gt;gvLogin = GoogleVoiceLogin()&lt;br /&gt;&lt;br /&gt;# Get the open (Cookie data still intact)&lt;br /&gt;opener = gvLogin.getOpener()&lt;br /&gt;&lt;br /&gt;# Get the _rnr_se value&lt;br /&gt;_rnr_se = gvLogin.getRnrSe()&lt;br /&gt;&lt;br /&gt;# Prompt for Text Message details&lt;br /&gt;phoneNumber = raw_input("Destination number: ")&lt;br /&gt;text = raw_input("Message to send: ")&lt;br /&gt;&lt;br /&gt;# Insert blank line&lt;br /&gt;print&lt;br /&gt;&lt;br /&gt;# Set up parameters for sending text&lt;br /&gt;sendTextParams = urllib.urlencode({&lt;br /&gt;    '_rnr_se': _rnr_se,&lt;br /&gt;    'phoneNumber': phoneNumber,&lt;br /&gt;    'text': text&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;# Send the text, display status message  &lt;br /&gt;response  = opener.open('https://www.google.com/voice/sms/send/', sendTextParams)&lt;br /&gt;if "true" in response.read():&lt;br /&gt;    print "Message successfully sent!"&lt;br /&gt;else:&lt;br /&gt;    print "Message failed!"&lt;br /&gt;response.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Similar scripts could easily be created to call people, or perform other tasks on your account.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-2654832898112871694?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/2654832898112871694/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/python-google-voice-revisited.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/2654832898112871694'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/2654832898112871694'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/python-google-voice-revisited.html' title='Python - Google Voice part 2'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-4459439774268264419</id><published>2009-07-13T13:10:00.000-07:00</published><updated>2010-01-23T10:25:19.707-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='google voice'/><category scheme='http://www.blogger.com/atom/ns#' term='command line'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='sms'/><category scheme='http://www.blogger.com/atom/ns#' term='regular expressions'/><category scheme='http://www.blogger.com/atom/ns#' term='google'/><title type='text'>Google Voice - Python - SMS</title><content type='html'>&lt;span style="font-weight: bold; color: rgb(153, 0, 0);font-size:130%;" &gt;EDIT: Google recently made some behind the scenes changes to the login page, which broke this script. &lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(153, 0, 0);font-size:130%;" &gt;Please see the new and improved script &lt;a href="http://everydayscripting.blogspot.com/2009/08/python-google-voice-mass-sms-and-mass.html"&gt;here&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;After a long wait, I finally received my &lt;a href="http://www.google.com/support/voice/bin/topic.py?hl=en&amp;amp;topic=16495"&gt;Google Voice&lt;/a&gt; invitation last weekend. It offers a lot of features, but one that I was really excited about was the free SMS service. I don't have a cell phone, but nearly all my family members, friends and co-workers do. The majority of them are more prone to check their phone than their email, so sending a text is the best way to contact them. For a while, I used Gmail's SMS Lab to get in contact with the leaders and the Scouts of a Boy Scout troop that I participate in, but Gmail shut that down recently.&lt;br /&gt;&lt;br /&gt;After I got my account, I began thinking about how neat it would be to be able to set up a script to send reminder text messages for me (preferably scheduled) using my account. I searched all over the internet to find out if there was an API provided by Google to do this -like their excellent &lt;a href="http://code.google.com/apis/gdata/"&gt;gdata API&lt;/a&gt; - but no luck. I didn't give up there, there had to be a way.&lt;br /&gt;&lt;br /&gt;A while ago, I learned that you can use HTTPCookieProcessor() from the urllib2 module to create an opener that will keep track of Cookie data sent back and forth from a server. So, with the help of &lt;a href="http://getfirebug.com/"&gt;Firebug &lt;/a&gt;(the best friend of many web-developers) I looked at the headers and POST data being sent when logging in to Google Voice, and when sending a text message. Turns out, it isn't very hard to log into your account, grab some necessary information from the Google Voice Home page (the inbox) and send a text.&lt;br /&gt;&lt;br /&gt;This script will do just that one thing - log in your account, load the homepage to get a hidden form field's value, and then send one text message to one number. It will still show up in your outbox, so you aren't losing anything by using this method. This ability opens up a lot of possibilities, especially when coupled with the Gdata Contacts API. I have a few little scripts that I will be creating to send out weekly reminders to other people - and might use Google App Engine to make it even more useful and automated.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# Get URL handling support&lt;br /&gt;import urllib2, urllib&lt;br /&gt;# Get regular expression support&lt;br /&gt;import re&lt;br /&gt;&lt;br /&gt;# Google Account login credentials&lt;br /&gt;email = 'YOUR_EMAIL'&lt;br /&gt;password = 'YOUR_PASSWORD'&lt;br /&gt;&lt;br /&gt;# Text message details&lt;br /&gt;sendToNumber = 'NUMBER_TO_SEND_A_TEXT_TO'&lt;br /&gt;messageToSend = 'I used Python to send this!'&lt;br /&gt;&lt;br /&gt;# Set up an opener with HTTPCookieProcessor&lt;br /&gt;opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())&lt;br /&gt;urllib2.install_opener(opener)&lt;br /&gt;&lt;br /&gt;# Set up login credentials for Google Accounts&lt;br /&gt;# The 'continue' param redirects us to the Google Voice &lt;br /&gt;# homepage, and gives us necessary cookie info&lt;br /&gt;loginParams = urllib.urlencode( {&lt;br /&gt;    'Email' : email,&lt;br /&gt;    'Passwd' : password,&lt;br /&gt;    'continue' : 'https://www.google.com/voice/account/signin',&lt;br /&gt;} )&lt;br /&gt;&lt;br /&gt;# Perform the login. Cookie info sent back will be saved, so we remain logged in&lt;br /&gt;# for future requests when using the opener&lt;br /&gt;opener.open( 'https://www.google.com/accounts/ServiceLoginAuth',  loginParams)&lt;br /&gt;&lt;br /&gt;# Need to load the homepage to find user specific data&lt;br /&gt;googleVoiceHomeData = opener.open('https://www.google.com/voice/#inbox').read()&lt;br /&gt;&lt;br /&gt;# Go through the home page and grab the value for the hidden&lt;br /&gt;# form field "_rnr_se", which must be included when sending texts&lt;br /&gt;match = re.search('name="_rnr_se".*?value="(.*?)"', googleVoiceHomeData)&lt;br /&gt;_rnr_se = match.group(1)&lt;br /&gt;&lt;br /&gt;# Set up parameters for sending text&lt;br /&gt;sendTextParams = urllib.urlencode({&lt;br /&gt;    '_rnr_se': _rnr_se,&lt;br /&gt;    'phoneNumber': sendToNumber,&lt;br /&gt;    'text': messageToSend&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;# Send the text, store the return value  &lt;br /&gt;f  = opener.open('https://www.google.com/voice/sms/send/', sendTextParams)&lt;br /&gt;data = f.read()&lt;br /&gt;print data&lt;br /&gt;f.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Right now, if it succeeds nothing spectacular happens - it simply prints out the response: {"ok":true,"data":{"code":0}}. If it fails, {"ok":false,"data":{"code":20}}. Just look at the "ok" value to see if it worked.&lt;br /&gt;&lt;br /&gt;You may notice that I don't have much (any) error checking in the script, nor do I usually when putting together scripts quickly just to test an idea out. When I do create something actually useful using this above script, I will put in the appropriate "checker" code. Just be aware that a text message can be 160 characters long, if you go over the remaining characters will be sent in a separate message.&lt;br /&gt;&lt;br /&gt;Have fun, and don't be evil.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-4459439774268264419?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/4459439774268264419/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/google-voice-python-sms.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/4459439774268264419'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/4459439774268264419'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/google-voice-python-sms.html' title='Google Voice - Python - SMS'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-1839143427608131113</id><published>2009-07-10T20:08:00.000-07:00</published><updated>2010-01-23T10:22:35.728-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pywin32'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='notepad++'/><category scheme='http://www.blogger.com/atom/ns#' term='pygments'/><title type='text'>Syntax Highlighting - quick and easy</title><content type='html'>&lt;span style="font-weight: bold;"&gt;Edit: I recently made my life easier by switching over to &lt;/span&gt;&lt;a style="font-weight: bold;" href="http://code.google.com/p/google-code-prettify/"&gt;http://code.google.com/p/google-code-prettify/&lt;/a&gt;&lt;span style="font-weight: bold;"&gt; for syntax highlighting.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since this blog is mainly about &lt;a href="http://python.org/"&gt;Python &lt;/a&gt;examples, I needed a way to post syntax highlighted Python code. There were a few solutions, including a hosted &lt;a href="http://code.google.com/p/syntaxhighlighter/"&gt;javascript option&lt;/a&gt;. I didn't like that one too much for a few reasons. After short struggle, I was able to install &lt;a href="http://pygments.org/"&gt;Pygments&lt;/a&gt; on my computer which is a Python module that can apply syntax highlighting to many different programming languages' source. I wanted a quick and easy way of writing Python code in my favorite text editor, &lt;a href="http://notepad-plus.sourceforge.net/uk/site.htm"&gt;Notepad++&lt;/a&gt;,  and getting it up on this blog. I thought it would be neat to create a syntax_highlighter.py script, and use the NppExec plug-in to run the syntax highlighter on the current document, and then paste the results on the clipboard. To do that last part, I had to download and install the &lt;a href="http://sourceforge.net/projects/pywin32/files/"&gt;pywin32 &lt;/a&gt;library (you can find that on sourceforge).  Here is my final script:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;from pygments import highlight&lt;br /&gt;from pygments.lexers import PythonLexer&lt;br /&gt;from pygments.formatters import HtmlFormatter&lt;br /&gt;import win32clipboard&lt;br /&gt;import sys,re&lt;br /&gt;&lt;br /&gt;# Replace tab characters with 4 spaces for better look on webpage&lt;br /&gt;def replaceTabs(matchobj):&lt;br /&gt;return "    "&lt;br /&gt;&lt;br /&gt;# Open up and store the souce file contents&lt;br /&gt;rawCode = open(sys.argv[1], "r").read()&lt;br /&gt;&lt;br /&gt;# Apply syntax highlighting&lt;br /&gt;highlightedCode = highlight(rawCode, PythonLexer(), HtmlFormatter())&lt;br /&gt;&lt;br /&gt;# Replace tabs with spaces&lt;br /&gt;finalCode = re.sub("\t", replaceTabs, highlightedCode)&lt;br /&gt;&lt;br /&gt;win32clipboard.OpenClipboard()&lt;br /&gt;win32clipboard.EmptyClipboard()&lt;br /&gt;win32clipboard.SetClipboardText(finalCode)&lt;br /&gt;win32clipboard.CloseClipboard()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This will put on my clipboard code that has been surrounded with the appropriate html tags to make it appear nicely formatted and highlighted. I created another function  to replace all tabs with 4 spaces to make it look better (I found it easier to do that with the regular expression module).&lt;br /&gt;&lt;br /&gt;If you use Notepad++, I used this to run the script on the current open file:&lt;br /&gt;&lt;pre&gt;python "FULL_PATH_TO_YOUR_HIGHLIGHTING_SCRIPT" "$(FULL_CURRENT_PATH)"&lt;br /&gt;&lt;/pre&gt;The first argument should be the full path to the syntax_highlight.py script - the next ,"$(FULL_CURRENT_PATH)",  is a standard variable that the NppExec plug-in uses to define the current open file. Once run, the result will be on your clipboard ready to be pasted. Once set up, having it ready to paste on this blog is a CTRL-F6 keystroke away. This could work for any language that Pygments supports.&lt;br /&gt;&lt;br /&gt;The clipboard portion of the script was a lot easier than I thought that it would be. The pywin32 library has many useful features in it - I know that &lt;a href="http://www.answermysearches.com/python-how-to-copy-and-paste-to-the-clipboard-in-linux/286/"&gt;similar solutions exist for Linux&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;NOTE:&lt;/span&gt; I  ran&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;from pygments import highlight&lt;br /&gt;from pygments.lexers import PythonLexer&lt;br /&gt;from pygments.formatters import HtmlFormatter&lt;br /&gt;&lt;br /&gt;print HtmlFormatter().get_style_defs('.highlight')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt; to get my CSS rules, which I was able to paste in my template.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-1839143427608131113?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/1839143427608131113/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/first-example-since-this-blog-is-mainly.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1839143427608131113'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/1839143427608131113'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/first-example-since-this-blog-is-mainly.html' title='Syntax Highlighting - quick and easy'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5484413222728812890.post-8733010854669360291</id><published>2009-07-10T09:25:00.000-07:00</published><updated>2009-07-12T20:40:01.236-07:00</updated><title type='text'>What is this?</title><content type='html'>Many of my co-workers and class-mates have been asking me what Python can actually be used for, so I decided to start this blog demonstrating some of the ways that I have been employing its power and simplicity.&lt;br /&gt;&lt;br /&gt;I am by no means a Python expert, but I like it quite a bit. I have been using it to perform little tasks at work and school for a while now and hope that by seeing some of my examples, you can see what it might be able to do for you. Each post here will contain a code snippet that I have created to help perform some task. I won't be really talking much about events happening in the Python community - there are plenty of other blogs for that. Here is a blog of simple, practical examples that might come in handy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5484413222728812890-8733010854669360291?l=everydayscripting.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://everydayscripting.blogspot.com/feeds/8733010854669360291/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/what-is-this.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/8733010854669360291'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5484413222728812890/posts/default/8733010854669360291'/><link rel='alternate' type='text/html' href='http://everydayscripting.blogspot.com/2009/07/what-is-this.html' title='What is this?'/><author><name>Scott Hillman</name><uri>http://www.blogger.com/profile/14751342469827168317</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://1.bp.blogspot.com/_7au8LV-2iSE/SnJAazKxghI/AAAAAAAAAUY/D8TI9y-HIUI/S220/IMG_2435.JPG'/></author><thr:total>1</thr:total></entry></feed>
