contactlog.py
9.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# $Id: contactlog.py,v 1.1 1998/07/31 03:59:59 connolly Exp $
# This code sort of works, at least to demonstrate python/Outlook
# integration. I got through two of the categories of stylized
# data that I keep in my Pilot Address book (caller-id info,
# and business cards; the others are email, post, etc.)
# Then I got bored.
# Especially watch out for stuff marked with @@.
#
# But I'm documenting it carefully,
# because I've been looking for these clues for
# *months* and I don't want to lose them.
# And I'm releasing it. Share and Enjoy.
# Dan Connolly
# <connolly@w3.org>
# http://www.w3.org/People/Connolly/
# Copyright © 1998 World Wide Web Consortium,
# (Massachusetts Institute of Technology, Institut National de
# Recherche en Informatique et en Automatique, Keio University). All
# Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software
# and its documentation for any purpose and without fee or
# royalty is hereby granted, per the terms and conditions in
#
# W3C IPR SOFTWARE NOTICE
# http://www.w3.org/COPYRIGHT
# September 1997
# This module depends on standard and built-in python modules, per
# Python Library Reference
# April 14, 1998
# Release 1.5.1
# http://www.python.org/doc/lib/lib.html
import string, StringIO
# It also uses the win32com package
# maintained by Mark Hammond
# http://www.python.org/windows/win32com/
#
# specifically, as documented in
# PythonWin Help
# Help file built: 04/26/98
# available in http://www.python.org/windows/win32all/win32all.exe
# Here we bind to the the actual Outlook 98 runtime,
# whose interface is documented in a
# Microsoft help file, "Microsoft Outlook Visual Basic"
#
# I found the help file via the Microsoft Knowledge Base:
#
# "OL98: How to Install Visual Basic Help"
# Last reviewed: April 1, 1998
# Article ID: Q183220
# http://support.microsoft.com/support/kb/articles/q183/2/20.asp
#
# The following lines are generated by makepy
#
#Outlook 98 Type Library
# {00062FFF-0000-0000-C000-000000000046}, lcid=0, major=8, minor=5
# Use these commands in Python code to auto generate .py support
from win32com.client import gencache
outlook = gencache.EnsureModule('{00062FFF-0000-0000-C000-000000000046}',
0, 8, 5)
# The only namespace supported in Outlook '98
# see "Microsoft Outlook Visual Basic"
MAPI = "MAPI"
# Hard-coded configuration
# probably should use sys.argv, but I didn't bother...
Inf = "C:\\winnt\\profiles\\connolly\\desktop\\980728pilot-addr.txt"
# for other folks to do testing, here are a couple lines from that file:
_test_data = \
"""Last Name First Name Job Title Company Business Phone Home Phone Business Fax Other Phone E-mail Street Address City State Zip/Postal Code Country Location Birthday User Field 1 User Field 2 User Field 3 Private Categories
"Abrahamson" "Dr. David M" "+353-1-608-1716" "cavid@cs.tcd.ie" "Trinity College" "Dublin 2, Ireland" "1996-11" "lunch 96-11-13 ISO HTML Boston lunch" "0" "Business Card"
"""
def main():
oapp = Pilot2Outlook()
#infp = open(Inf)
infp = StringIO.StringIO(_test_data)
i = 0
while 1:
row = nextrow(infp)
if not row: break
i = i + 1
if i > 500: break #@@ can't figure out how
# to break a runaway script in PythonWin,
# so I added this little hack
print "@@add:", row
oapp.addPilotAddress(row)
# outlook.Application is the Application class, per
# the type library and help file cited above.
#
# It integrates completely seamlessly into the python
# object model.
#
# Hence, for methods such as GetNamespace, see
# the help file. I found the following article quite
# helpful as well. The examples are in visual basic, but
# the translation to python is straightforward.
#
# The Microsoft Outlook 97 Automation Server Programming Model
# Last Updated: June 26, 1998
# http://www.microsoft.com/OutlookDev/Articles/Outprog.htm
#
class Pilot2Outlook(outlook.Application):
def findContact(self, filter):
contacts = self.GetNamespace(MAPI).GetDefaultFolder(outlook.OlDefaultFolders.olFolderContacts)
return contacts.Items.Find(filter)
def incomingCall(self, name, num, isotime):
# convert caller-id format: 333-555-1212
# to Outlook format: (333) 555-1212
if len(num) == 12:
num = "(%s) %s" % (num[:3], num[4:])
ji = self.CreateItem(outlook.OlItemType.olJournalItem)
ji.Type = "Phone call" # set icon?
# stuff the original caller-id data away somewhere
# I'm not confident this UserProperties code
# works... wierdness around byRef args or something.
# I could never
# find the userproperties for a journal item
# in the Outlook GUI to check one way or the other.
p = ji.UserProperties.Add("CallerIDName", outlook.OlUserPropertyType.olText)
p.Value = name
p = ji.UserProperties.Add("CallerIDNumber", outlook.OlUserPropertyType.olText)
p.Value = num
#@@ mobile, other phone numbers...
contact = self.findContact('[Business Phone] = "%s" or [Home Phone] = "%s"' % (num, num))
if contact:
if contact.FullName:
name = contact.FullName
ji.Companies = contact.CompanyName
else:
contact = self.CreateItem(outlook.OlItemType.olContactItem)
contact.FullName = name
contact.BusinessTelephoneNumber = num
contact.Save()
ji.Recipients.Add(name)
t = iso2vb(isotime)
ji.Start = t
ji.Subject = "Call from %s %s" % (name or num, t)
ji.Save()
#ji.Display() #@@
return ji, contact
def logBusinessCard(self,
family,given,title,company,work,fax,email,
street,city,state,zip,country,isodate,note):
contact = self.findContact(
'[Last Name] = "%s" and [First Name] = "%s" and [Company] = "%s"' % (family, given, company))
if not contact:
print "@@ new contact"
contact = self.CreateItem(outlook.OlItemType.olContactItem)
contact.LastName = family
contact.FirstName = given
contact.CompanyName = company
contact.JobTitle = title
contact.BusinessTelephoneNumber = work
contact.BusinessFaxNumber = fax
contact.BusinessAddressStreet = street
contact.BusinessAddressCity = city
contact.BusinessAddressState = state
contact.BusinessAddressPostalCode = zip
contact.BusinessAddressCountry = country
#@@ don't clobber email
contact.Email1Address = email
if isodate:
ji = self.CreateItem(outlook.OlItemType.olJournalItem)
ji.Start = iso2vb(isodate)
ji.Type = "Conversation" # or perhaps meeting?
ji.Recipients.Add(contact.FullName)
ji.Companies = company
ji.Body = note
ji.Subject = "Business Card %s %s" % (contact.FullName, company)
#ji.Display() #@@
#@@ include scanned image of card?
ji.Save()
else:
ji = None
if note:
if contact.Body:
contact.Body = contact.Body + '\n' + note
else:
contact.Body = note
contact.Save()
return ji, contact
def addPilotAddress(self, row):
family,given,title,company,work,home,fax,other,email, \
street,city,state,zip,country,airport,birthday, \
updated,expired,note,private,category = row
if category == "Caller-id":
ji, c = self.incomingCall(family, work, updated)
if note:
ji.Body = note
#@@ translate private to Sensitivity
ji.Save()
if given or title or company or home \
or fax or other or email or street \
or city or state or zip or country or airport \
or birthday or expired:
print "@@info besides family, work, updated, note, private:",\
row
elif category == "Business Card":
if updated[-5:] == " card":
updated = updated[:-5]
ji, c = self.logBusinessCard(family,given,title,company,
work,fax,email,
street,city,state,zip,country,updated,note)
ji.Display() #@@debugging
c.Display()
#@@ other fields: home phone, airport?
# birthday, expired, private
else:
print "@@category not yet supported:", category, family, given, company
def iso2vb(isotime):
# YYYY-MM-DDTHH:MM:SS
# raises ??? exception when parts are missing?
year = isotime[:4]
month = isotime[5:7]
day = isotime[8:10] or '1' ## @# only month given
hour = isotime[11:13]
minute = isotime[14:16]
second = isotime[17:19]
ret = month + '/' + day + '/' + year
if hour and minute:
ret = ret + ' ' + hour + ':' + minute
if second:
ret = ret + ':' + second
print "@@convdate: ", isotime, ret
return ret
def nextrow(fp):
# A simple state-machine implementation of Windows
# tab-delimited file format with quoted fields.
# I'm not sure if this really
# does CRLF's right.
# There are faster ways to do this, I'm sure...
s = 'start'
row = ()
field = ""
while 1:
c = fp.read(1)
if not c:
if field or len(row):
raise IOError, "bad end of file"
return None
if s == 'start':
if c == "\n":
row = row + (field,)
return row
elif c == "\t":
row = row + (field,)
field = ''
elif c == '"':
s = 'quoted'
else:
field = field + c
elif s == 'quoted':
if c == '"':
s = 'start'
elif c == "\\":
s = 'escaped'
else:
field = field + c
elif s == 'escaped':
field = field + c
s = 'quoted'
else:
raise RuntimeError, 'bad case'
if __name__ == '__main__': main()