1 #!/usr/bin/env python 2 import os, re, time, string, cPickle, tempfile, fileinput 3 4 # Send an email out to everyone about a new photo album on Yahoo! photos. 5 # 6 # For each set of photographs: 7 # 8 # FIRST EXECUTION OF SCRIPT: 9 # Everyone gets an email. 10 # 11 # SUBSEQUENT EXECUTIONS: 12 # New email addresses added to distribution list (since first 13 # execution) will get an email; people who already got an email 14 # as a result of a prior execution will NOT get another one. 15 # ..................................................................... 16 # 17 # 1st version: Sun Apr 1, 2001 18 # ..................................................................... 19 20 info = {} 21 info['test'] = 't' 22 info['test'] = None 23 24 # Message: 25 info['mirror_message'] = 'message' 26 info[info['mirror_message']] = """Hello, This album contains photos taken while working as a courier for a California labor union ( http://www.cops.cc/ ) in the greater Los Angeles area. It covers the months of September and October whereas during January-August, while not working, I was able to do one roll per month. ROLL: __roll__ DATE: __date__ DESC: __description__ URI: __URI__ ............................................................ If you do not want to receive these emails, write: cymbaLa@Lafn.org Know someone who might like to get these photo emails? ...send their address. Is there something in California you'd like to see in a future album? ...send your idea! ............................................................""" 48 49 # Roll info: 50 dict = { 51 -1: 52 { 'roll': '__roll__', 53 'date': '__date__', 54 'description': '__description__', 55 'URI': '__URI__'}, 56 8: 57 { 'roll': 'paradise', 58 'date': 'October 2001', 59 'description': 'Los Angeles October', 60 'URI': 'http://www.lafn.org/~cymbala/photos.html'}, 61 7: 62 { 'roll': 'giraffes', 63 'date': 'August 2001', 64 'description': 'Los Angeles August', 65 'URI': 'http://www.lafn.org/~cymbala/photos.html'}, 66 6: 67 { 'roll': 'peerless', 68 'date': 'July 2001', 69 'description': 'Mister Chipy and Friends', 70 'URI': 'http://www.lafn.org/~cymbala/photos.html'}, 71 5: 72 { 'roll': 'brothers', 73 'date': 'June 2001', 74 'description': 'LA Streets and X-Country Drive', 75 'URI': 'http://www.lafn.org/~cymbala/photos.html'}, 76 4: 77 { 'roll': 'romero', 78 'date': 'May 2001', 79 'description': 'Mission District & Coit Tower Murals', 80 'URI': 'http://www.lafn.org/~cymbala/photos.html'}, 81 3: 82 { 'roll': 'slopes', 83 'date': 'April 2001', 84 'description': 'Flowery Slopes & LA Scenes', 85 'URI': 'http://www.lafn.org/~cymbala/photos.html'}, 86 2: 87 { 'roll': 'pipes', 88 'date': 'March 2001', 89 'description': 'Arizona Wildflowers', 90 'URI': 'http://www.lafn.org/~cymbala/photos.html'}, 91 1: 92 { 'roll': 'pantry', 93 'date': 'February 2001', 94 'description': 'LA Streets and Arizona Wildflowers', 95 'URI': 'http://photos.yahoo.com/rcymbala'}, 96 0: 97 { 'roll': '__roll__', 98 'date': '__date__', 99 'description': '__description__', 100 'URI': '__URI__'} 101 } 102 # 103 info['data'] = dict 104 105 # Subject: 106 info['header_subject_uppercase'] = 'Photo ALBUM:' 107 108 # Time: 109 info['time'] = time.strftime( 110 "%Y%m%d.%H%M.%S", time.localtime(time.time())) 111 112 # Database: 113 info['database_pathfile'] = os.path.expanduser( 114 '~/Corresp/photos.cpickle') 115 116 # Address book: 117 info['addresses_pathfile_test'] = os.path.expanduser( 118 '~/Corresp/photos_test.email') 119 info['addresses_pathfile'] = os.path.expanduser( 120 '~/Corresp/photos.email') 121 122 # NOTE: 123 # This assumes the alias `me' (~/.mailrc: alias me cymbaLa@Lafn.org) 124 # which is used as recipient; everyone else gets a blind-carbon-copy. 125 126 class Program: 127 128 def __init__(self): 129 # 130 self.MAIL_COMMAND = 'mail' 131 self.tempfile_pathfile = tempfile.mktemp() 132 pass 133 134 def __call__(self, info): 135 136 # --- 137 # Changes to information. 138 # Creates self.info and parses address book. 139 self.info_change(info) 140 info = '' 141 142 # --- 143 # Check files (do after `self.info_change') 144 self.check_files() 145 146 # --- 147 # Get pickle 148 self.my_cPickle('get') 149 150 # --- 151 # This will erase record of previous sents/sends. 152 # USE WITH CAUTION. 153 # The next line can result in duplicates being sent. 154 # ## self.pickle['data'][self.info['roll_number_max']]['sent'] = {} 155 156 # --- 157 # These will prevent sending and show useful information. 158 # ## raise str(self.pickle) 159 # ## raise str(self.info['addresses'].keys()) 160 161 # --- 162 # Send emails 163 self.send_emails(info) 164 165 # --- 166 # Store database in a pickle. 167 self.pickle_marinate() 168 self.my_cPickle('put') 169 170 pass 171 172 def send_emails(self, info): 173 # 174 # Initialize 175 sent = {} 176 if self.info['roll_number_max'] in self.pickle[ 177 'data'].keys(): 178 # 179 if 'sent' in self.pickle[ 180 'data'][self.info['roll_number_max']].keys(): 181 # 182 sent = self.pickle[ 183 'data'][self.info['roll_number_max']]['sent'] 184 pass 185 pass 186 187 # Add addresses. 188 for address in self.info['addresses'].keys(): 189 if not address in sent.keys(): 190 # 191 # address is all UPPERCASE letters. 192 sent[address] = { 193 'address': self.info['addresses'][address], 194 'flag': None} 195 pass 196 197 # Send if flag is not None... 198 199 # ``man mail'' 200 bcc_addr = [] 201 202 for key in sent.keys(): 203 if (sent[key]['flag'] == None or \ not self.info['test'] == None): 205 address = sent[key]['address'] 206 spam = 'Sending...' 207 print spam, key 208 print ' ' * len(spam), address 209 210 # List of addresses to receive email 211 bcc_addr.append(address) 212 213 # This prevents sending another copy if script run again 214 sent[key]['flag'] = self.info['time'] 215 216 pass 217 pass 218 219 # Save information across executions 220 self.info['data'][ 221 self.info['roll_number_max']][ 222 'sent'] = sent 223 224 if len(bcc_addr) > 0: 225 # 226 # ``man mail'' 227 booleybooley = self.MAIL_COMMAND 228 229 # Subject 230 subject = string.join( 231 (self.info['header_subject_uppercase'], 232 self.info['data'][ 233 self.info['roll_number_max']][ 234 'description'])) 235 subject = string.join(('', subject, ''), '"') 236 # 237 booleybooley = string.join( 238 (booleybooley, 239 string.join(('-s', 240 subject), 241 ' ')), 242 ' ') 243 244 # Send blind carbon copies to list 245 booleybooley = string.join( 246 (booleybooley, 247 string.join(('-b', 248 string.join(bcc_addr, ',')), 249 ' ')), 250 ' ') 251 252 # Send email to me 253 booleybooley = string.join( 254 (booleybooley, 255 'me'), 256 ' ') 257 258 # Store message to file to prepare for "cat". 259 file = open(self.tempfile_pathfile, 'w') 260 file.write(self.info[self.info['mirror_message']]) 261 file.close() 262 263 booleybooley = string.join( 264 ('cat ' + self.tempfile_pathfile, 265 booleybooley), 266 ' | ') 267 268 # raise booleybooley 269 # 270 # EXAMPLE: 271 # cat /tmp/@10262.1 | mail -s "PHOTO GALLERY: LA Streets 272 # and Arizona Wildflowers" -b 273 # cymbala@juno.com,rcymbala@yahoo.com,cymbaLa@Lafn.org me 274 275 # Hello, world! 276 booleybooley_rc = os.system(booleybooley) 277 278 if not booleybooley_rc == 0: 279 raise 'Failed:', booleybooley 280 281 pass 282 pass 283 284 def pickle_marinate(self): 285 # 286 # Do pickle house-cleaning before storing pickle 287 288 # Keep copy of message 289 self.info['data'][ 290 self.info[ 291 'roll_number_max']][ 292 'message'] = self.info['message'] 293 del self.info['message'] 294 295 # Delete every key except 'data' and disk location of pickle 296 for key in self.info.keys(): 297 if not ( 298 key == 'data' or 299 key == 'database_pathfile' 300 ): 301 del self.info[key] 302 pass 303 pass 304 pass 305 306 def my_cPickle(self, action): 307 # 308 if action == 'get': 309 file = open(self.info['database_pathfile'], 'r') 310 u = cPickle.Unpickler(file) 311 312 # !!!!! 313 self.pickle = u.load() 314 315 file.close() 316 pass 317 elif action == 'put': 318 file = open(self.info['database_pathfile'], 'w') 319 p = cPickle.Pickler(file) 320 321 # !!!!! 322 p.dump(self.info) 323 324 file.close() 325 pass 326 else: 327 raise 'Action not recognized: ' + action 328 pass 329 330 def info_change(self, info): 331 # 332 if not info['test'] == None: 333 info['addresses_pathfile'] = info[ 334 'addresses_pathfile_test'] 335 pass 336 337 # Find highest roll number. 338 info['roll_number_max'] = 0 339 for number in info['data'].keys(): 340 if number > info['roll_number_max']: 341 info['roll_number_max'] = number 342 pass 343 pass 344 345 # Get email addresses 346 info = self.parse_address_book(info) 347 348 # Replace __placeholders__ with meaningful information. 349 info = self.replace_placeholders(info) 350 351 # Move information to an attribute of self (original 352 # information is deleted after calling this function). 353 self.info = info 354 pass 355 356 def replace_placeholders(self, info): 357 key = info['mirror_message'] 358 message = info[key] 359 360 locations = {} 361 message_length = len(message) 362 363 for i in range(message_length): 364 char0 = message[i] 365 366 if (i + 1) < message_length: 367 char1 = message[i+1] 368 pass 369 else: 370 char1 = None 371 pass 372 373 if char0 == '_' and char1 == '_': 374 # 375 # Is it an opening "__" or a closing "__"? 376 stop = i + 50 377 if stop > message_length: 378 stop = message_length 379 pass 380 pieces = string.split(message[i:stop], '__') 381 382 key = pieces[1] 383 # 384 # Look for key in info: 385 if key in info[ 386 'data'][ 387 info[ 388 'roll_number_max']].keys( 389 ): 390 391 locations[key] = [i, 392 i + \ len('__' * 2) + \ len(key)] 395 pass 396 pass 397 pass 398 info['locations'] = locations 399 400 # Do replacements 401 for key in locations.keys(): 402 403 replacement = info['data'][ 404 info[ 405 'roll_number_max']][ 406 key] 407 408 start = locations[key][0] 409 stop = locations[key][1] 410 placeholder = message[start:stop] 411 412 # Replace 413 message = message[:start] + replacement + message[stop:] 414 locations[key] = (-1, -1) 415 416 # Adjust locations if necessary 417 for wooley in locations.keys(): 418 if locations[wooley][0] > start: 419 adjustment = len(replacement) - len(placeholder) 420 locations[wooley][0] = locations[wooley][0] + adjustment 421 locations[wooley][1] = locations[wooley][1] + adjustment 422 pass 423 pass 424 pass 425 426 # Same as what happened at beginning of function, only reversed. 427 key = info['mirror_message'] 428 info[key] = message 429 430 return info 431 432 def parse_address_book(self, info): 433 # 434 # alias cymbala-r_juno cymbala@juno.com 435 # alias cymbala-r_lafn cymbaLa@Lafn.org 436 # alias cymbala-r_yahoo rcymbala@yahoo.com 437 # alias me cymbala-r_lafn 438 # 439 re_alias = re.compile(r'^[\s]*alias', re.I) 440 441 addresses = {} 442 aliases = {} 443 for line in fileinput.input(info['addresses_pathfile']): 444 if re_alias.match(line): 445 # Delete trailing newline 446 line = line[:-1] 447 pieces = string.split(line) 448 alias_name = pieces[1] 449 alias_name_upper = string.upper(alias_name) 450 email_address = pieces[2] 451 email_address_upper = string.upper(email_address) 452 # 453 aliases[alias_name_upper] = email_address_upper 454 addresses[email_address_upper] = email_address 455 pass 456 457 # If no ``@'' attempt to resolve address using other aliases. 458 # Assumes, in general, that aliases don't have ``@'' in alias name. 459 last_key_missing_at = None 460 last_key_missing_count = 0 461 flipper = 't' 462 while flipper: 463 flipper = None 464 for key_upper_a in addresses.keys(): 465 if string.find(addresses[key_upper_a], '@') == -1: 466 flipper = 't' 467 for key_upper_b in aliases.keys(): 468 if key_upper_b == key_upper_a: 469 # 470 # Remove resolved alias, that's it, nothing else. 471 del addresses[key_upper_a] 472 pass 473 pass 474 475 if key_upper_a == last_key_missing_at: 476 last_key_missing_count = last_key_missing_count + 1 477 if last_key_missing_count > 5: 478 raise 'Cannot resolve alias: ' + key_upper_a 479 pass 480 last_key_missing_at = key_upper_a 481 pass 482 pass 483 pass 484 485 info['addresses'] = addresses 486 return info 487 488 def check_files(self): 489 # 490 # Look for pathfiles. 491 for key in self.info.keys(): 492 if key[-8:] == 'pathfile': 493 pathfile = self.info[key] 494 if not os.path.isfile(pathfile): 495 if pathfile[-7:] == 'cpickle': 496 # 497 # Create an empty pickle. 498 self.my_cPickle('put') 499 pass 500 else: 501 raise 'File not found: ' + pathfile 502 pass 503 pass 504 pass 505 pass 506 507 # MAIN: 508 wooleywooley = Program() 509 wooleywooley(info) 510 511 ### 512 # 513 # Local variables: 514 # py-indent-offset: 4 515 # End: |