#!/usr/bin/python # # # This is SMPy, the Pythonified Perlific SMS Sender # # _____ _ _ _____ py # / ____) | \ / | / ____) # ( ( | \/ | ( ( # ( (___ | |``| | ( (___ # \___ \ | | | | \___ \ # ) ) (.) (.) ) ) # ____) ) ____) ) # (_____/ pythonified (_____/ # # or: Send SMS using a PyGTK-based GUI using A1.net # # Copyright 2007-2008 Thomas Perl # Based on smp, Copyright 2006 Thomas Perl # License: GNU General Public License v2 or later # # See http://thpinfo.com/2007/hacks/ for more info. # # Updated: # 2007-10-06: Make smpy work with python-evolution 0.0.3 # 2007-10-13: Add window border, window icon + new title # 2007-12-26: Make evolution support optional (fallback) # 2008-01-02: Hide search entry when Evo support is missing # import evolution import gobject import mechanize import threading import time import shelve import os.path import sys import gtk class PythonifiedSMSSender(object): host = 'sms.a1.net' def __init__( self): self.SMSWindow = gtk.Window() self.SMSWindow.connect( 'destroy', gtk.main_quit) sh = shelve.open( os.path.expanduser( '~/.smpyrc')) self.username = sh['username'] self.password = sh['password'] sh.close() try: contact_list = self.get_contact_list() except: print >>sys.stderr, 'Warning: Cannot open evolution address book' contact_list = {} contacts_model = self.contacts_to_model(contact_list) vb = gtk.VBox() vb.set_spacing( 6) vb.set_border_width( 12) self.SMSWindow.add( vb) if len(contact_list): hb = gtk.HBox() hb.set_spacing(6) vb.pack_start(hb, False) search_icon = gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON) hb.pack_start(search_icon, False) self.search_entry = gtk.Entry() completion = gtk.EntryCompletion() self.search_entry.set_completion(completion) completion.set_model(contacts_model) completion.set_text_column(0) completion.set_match_func(self.completion_match_func, None) completion.connect('match-selected', self.match_selected) hb.pack_start(self.search_entry, True) self.search_entry.set_text('Enter phonebook name') self.search_entry.select_region(0, -1) self.search_entry.grab_focus() self.number = gtk.Entry() vb.pack_start( self.number, False) if len(contact_list) == 0: self.number.set_text('Enter phone number (+43...)') self.number.select_region(0, -1) self.number.grab_focus() self.message = gtk.TextView() self.message.set_wrap_mode( gtk.WRAP_WORD) frame = gtk.Frame() frame.set_shadow_type( gtk.SHADOW_IN) frame.add( self.message) vb.pack_start( frame, True) self.status = gtk.Label( 'smpy for %s at %s' % ( self.username, self.host, )) self.status.set_alignment( 0.0, 0.5) vb.pack_start( self.status, False) hb = gtk.HBox() hb.set_spacing( 6) vb.pack_start( hb, False) self.send_button = gtk.Button( label = 'Send SMS') self.send_button.set_image( gtk.image_new_from_icon_name( 'stock_mail-send', gtk.ICON_SIZE_BUTTON)) self.send_button.connect( 'clicked', self.send_sms) hb.pack_end( self.send_button, False) close_button = gtk.Button( stock = gtk.STOCK_CLOSE) close_button.connect( 'clicked', gtk.main_quit) hb.pack_end( close_button, False) self.SMSWindow.resize( 300, 250) self.SMSWindow.move( 200, 200) self.SMSWindow.set_title( 'smpy :)') self.SMSWindow.set_icon_name( 'stock_mail-send') self.SMSWindow.set_type_hint( gtk.gdk.WINDOW_TYPE_HINT_DIALOG) self.SMSWindow.show_all() def completion_match_func( self, completion, key, iter, column): text = completion.get_model().get_value( iter, 0) matched = True for part in key.split(): if text.lower().find( part.lower()) == -1: matched = False return matched def match_selected( self, completion, model, iter): self.number.set_text( model.get_value( iter, 1)) gobject.idle_add( self.message.grab_focus) def send_sms( self, widget): self.send_button.set_sensitive( False) threading.Thread( target = self.send_sms_thread).start() def send_sms_thread( self): buf = self.message.get_buffer() message = buf.get_text( buf.get_start_iter(), buf.get_end_iter()) number = self.number.get_text() for message in self.send_message( number, message): gobject.idle_add( lambda: self.status.set_text( message)) time.sleep( 3) gobject.idle_add( gtk.main_quit) def contacts_to_model( self, contacts): model = gtk.ListStore( str, str) for person in sorted( contacts.keys()): for ( type, number ) in contacts[person]: if len(contacts[person]) > 1: caption = '%s (%s)' % ( person, self.cute_type( type) ) else: caption = person model.append( [ caption, number ]) return model def cute_type( self, type): return type[0].upper() + type[1:].replace( '-', ' ') def get_contact_list( self): props = gobject.list_properties( evolution.ebook.EContact) pp = [ p.name for p in props if 'phone' in p.name ] contacts = {} for contact in evolution.ebook.open_addressbook('default').get_all_contacts(): gp = contact.get_property numbers = [ ( p, gp(p) ) for p in pp if gp(p) ] if len(numbers): contacts[gp('full-name')] = numbers return contacts def send_message( self, to, msg): b = mechanize.Browser() yield 'Connecting to %s...' % self.host b.open('http://%s/' % self.host) yield 'Logging in as "%s"...' % self.username b.select_form( name = 'asmpform') b['UserID'] = self.username b['Password'] = self.password b.submit() yield 'Sending message to %s...' % to b.select_form( name = 'myform') b['address'] = to b['msg'] = msg b.submit() yield 'Message sent :)' if __name__ == '__main__': if sys.argv[-1].find('config') != -1: print '** SMPY CONFIGURATION **' sh = shelve.open( os.path.expanduser( '~/.smpyrc')) sh['username'] = raw_input( 'Username: ') sh['password'] = raw_input( 'Password: ') sh.close() sys.exit( 0) if not os.path.exists( os.path.expanduser( '~/.smpyrc')): print 'Error: You have to run "%s --configure" first!' % ( sys.argv[0], ) sys.exit( 1) sender = PythonifiedSMSSender() gobject.threads_init() gtk.main()