]> git.deb.at Git - pkg/abook.git/blob - contrib/mail2abook.py
Imported Upstream version 0.4.16
[pkg/abook.git] / contrib / mail2abook.py
1 #!/usr/bin/env python
2
3 # NOTE: starting from abook version 0.4.15 it is often more convenient
4 # to use command line option "--add-email" instead of this script
5
6 # mail2abook.py version 0.1pre4
7 # by Jaakko Heinonen <jheinonen@users.sourceforge.net>
8 # based on Moritz Moeller-Herrmann's mail2muttalias.py
9
10 # Description:
11 # this script reads all mail adresses from a piped mailfile and
12 # offers to write them to the ABOOKFILE specified in the script
13 # Append the following lines or similiar to your muttrc:
14 # macro pager A |'path to script'/mail2abook.py\n
15
16 # Then press A in pager mode to add an address to your abook addressbook
17
18 # Here's the original copyright information:
19
20 # RELEASE 0.5
21 ##
22 # Needs python 1.5 and cursesmodule 1.4 by andrich@fga.de (Oliver Andrich)   
23 # available somewhere on http://www.python.org or directly from
24 # http://andrich.net/python/selfmade.html
25 #
26 # Copyright by Moritz Moeller-Herrmann <mmh@gmnx.net>
27 #
28 # Homepage (check for changes before reporting a bug)
29 # http://webrum.uni-mannheim.de/jura/moritz/mail2muttalias.html 
30 #
31 # Use this any way you want. Please inform me of any error/improvement
32 # and leave the copyright notice untouched.
33 # No warranties, as this is my first program :-)
34 # Works for me on Linux with Python 1.5+
35
36 # Thanks to Josh Hildebrand for some improvements
37 ##
38
39 # Known bugs: 
40 # If name containing @ is quoted("") before a mail adress, 
41 # the program will not catch the whole name, 
42 # but who would alias such an idiot?
43 # If you get a problem with "unknown variable / keyword error" 
44 # with KEYUP and KEYDOWN, either get a newer pythoncurses module 
45 # or change them to lowercase.
46 #
47 # Probably some more, mail me if you find one!
48
49 import re 
50 import string, sys, os 
51 import curses, traceback
52
53
54 ABOOKFILE=os.environ["HOME"] + "/.abook.addressbook"
55
56 try :
57         testcurses = curses.KEY_UP
58 except NameError:
59         print "Your pythoncurses module is old. Please upgrade to 1.4 or higher."
60         print "Alternative: Modify the script. Change all 6 occurences of curses.KEY_UP"
61         print "and similiar to lowercase."
62
63 try : testAF = ABOOKFILE #test if ALIASFILE was configured in script
64 except NameError:
65         try: ALIASFILE=os.environ["ABOOKFILE"] #test is environment MUTTALIASFILE was set
66         except KeyError:
67                 print "Please specify ABOOKFILE at beginning of script \nor set environment variable ABOOKFILE"
68                 print "Aborting ..."
69                 sys.exit()
70
71
72
73 ###Thanks for the following go to Michael P. Reilly
74 if not sys.stdin.isatty():  # is it not attached to a terminal?
75   file = sys.stdin.read()
76   sys.stdin = _dev_tty = open('/dev/tty', 'w+')
77   # close the file descriptor for stdin thru the posix module
78   os.close(0)
79   # now we duplicate the opened _dev_tty file at file descriptor 0 (fd0)
80   # really, dup creates the duplicate at the lowest numbered, closed file
81   # descriptor, but since we just closed fd0, we know we will dup to fd0
82   os.dup(_dev_tty.fileno())  # on UNIX, the fileno() method returns the
83                              # file object's file descriptor
84 else:  # is a terminal
85         print "Please use as a pipe!"
86         print "Aborting..."
87         sys.exit()
88 # now standard input points to the terminal again, at the C level, not just
89 # at the Python level.
90
91
92 print "Looking for mail adresses, this may take a while..."
93
94
95 ####  define functions
96
97
98 class screenC:
99         "Class to create a simple to use menu using curses"
100         def __init__(self):
101                 import curses, traceback
102                 self.MAXSECT = 0
103                 self.LSECT = 1
104                 # Initialize curses
105                 self.stdscr=curses.initscr()
106                 # Turn off echoing of keys, and enter cbreak mode,
107                 # where no buffering is performed on keyboard input
108                 curses.noecho() 
109                 curses.cbreak()
110
111                 # In keypad mode, escape sequences for special keys
112                 # (like the cursor keys) will be interpreted and
113                 # a special value like curses.KEY_LEFT will be returned
114                 self.stdscr.keypad(1)           
115         
116         def titel(self,TITEL="Title  - test",HELP="Use up and down arrows + Return"):   
117                 "Draws Title and Instructions"
118                 self.stdscr.addstr(0,0,TITEL,curses.A_UNDERLINE)                                # Title + Help
119                 self.stdscr.addstr(self.Y -2 ,0,HELP,curses.A_REVERSE)
120
121         def refresh(self):
122                 self.stdscr.refresh()
123
124         def size(self):
125                 "Returns screen size and cursor position"
126                 #Y, X = 0, 0
127                 self.Y, self.X = self.stdscr.getmaxyx()
128                 #self.y, self.x = 0, 0
129                 self.y, self.x = self.stdscr.getyx()
130                 #print Y, X
131                 return self.Y, self.X, self.y, self.x
132                 
133
134         def showlist(self,LISTE,LSECT=1):
135                 "Analyzes list, calculates screen, draws current part of list on screen "
136                 s = self.Y -3                                                           #space on screen
137                 self.MAXSECT=1
138                 while len(LISTE) > self.MAXSECT * s :           # how many times do we need the screen?
139                         self.MAXSECT = self.MAXSECT +1
140                         
141                 if self.LSECT > self.MAXSECT:                           #check for end of list
142                         self.LSECT = LSECT -1
143                 
144                 if self.LSECT <=        0:                                              # 
145                         self.LSECT =1           
146                 
147                 if len(LISTE) <= s:
148                         self.LISTPART=LISTE
149                 
150                 else :
151                         self.LISTPART=LISTE[s * ( self.LSECT -1 ) : s * self.LSECT ]    # part of the List is shown
152                 
153                 self.stdscr.addstr(self.Y -2, self.X -len(`self.LSECT`+`self.MAXSECT`) -5, "(" + `self.LSECT` + "/" + `self.MAXSECT` + ")")
154                 #if len(LISTE) > self.Y - 3:
155                 #       return 1
156                 for i in range (1, self.Y -2):                  # clear screen between title and help text
157                         self.stdscr.move(i , 0)
158                         self.stdscr.clrtoeol()
159                 for i in range (0,len(self.LISTPART)):          # print out current part of list
160                         Item = self.LISTPART[i]
161                         self.stdscr.addstr(i +1, 0, Item[:self.X])
162         
163         def getresult(self,HOEHE):
164                 "Get Result from cursor position"
165                 RESULT= self.LISTPART[(HOEHE -1)]
166                 return RESULT
167         
168         def showresult(self, HOEHE, DICT={}):
169                 "Look up result to show in dictionary if provided, return list member otherwise"
170                 if DICT=={}:
171                         return self.getresult(HOEHE)
172                 else :
173                         return string.join(DICT[self.getresult(HOEHE)], " || ")
174                 
175
176         
177         def menucall(self, LISTE, DICT={}, TITEL="",HELP="Use up and down arrows, Return to select"):
178                 "Takes a list and offers a menu where you can choose one item, optionally, look up result in dictionary if provided"
179                 REFY=1
180                 self.__init__()
181                 self.size()
182                 self.titel(TITEL,HELP)
183                 self.showlist(LISTE)
184                 self.refresh()
185                 self.stdscr.move(1,0)
186                 while 1:                                                                        # read Key events 
187                         c = self.stdscr.getch()
188                         self.size()
189                         
190                         #if c == curses.KEY_LEFT and self.x  > 0:
191                         #       self.stdscr.move(self.y, self.x -1); REFY = 1 # REFY == refresh: yes
192                         
193                         #if c == curses.KEY_RIGHT               and self.x < self.X -1:
194                         #       #if x < 4 and LENGTH - ZAHLY > y - 1: 
195                         #       self.stdscr.move(self.y, self.x + 1); REFY = 1
196                         
197                         if c == curses.KEY_UP or c == 107: #up arrow or k
198                                 if self.y > 1:
199                                         self.stdscr.move(self.y -1, self.x); REFY = 1
200                                 else :
201                                         self.LSECT=self.LSECT-1
202                                         self.showlist(LISTE,self.LSECT)
203                                         self.stdscr.move(len(self.LISTPART), 0)
204                                         REFY = 1
205                                 
206                         if c == curses.KEY_DOWN or c == 106: #down arrow or j
207                                 
208                                 if self.y < len(self.LISTPART) :        
209                                         self.stdscr.move(self.y +1, self.x); REFY = 1
210
211                                 else :
212                                         self.LSECT=self.LSECT+1
213                                         self.showlist(LISTE,self.LSECT)
214                                         self.stdscr.move(1,0)
215                                         REFY = 1
216                         
217                         if c == curses.KEY_PPAGE:
218                                 self.LSECT=self.LSECT-1
219                                 self.showlist(LISTE,self.LSECT)
220                                 self.stdscr.move(1, 0)
221                                 REFY = 1
222
223                         if c == curses.KEY_NPAGE:
224                                 self.LSECT=self.LSECT+1
225                                 self.showlist(LISTE,self.LSECT)
226                                 self.stdscr.move(1,0)
227                                 REFY = 1
228
229                         if c == curses.KEY_END:
230                                 self.LSECT=self.MAXSECT
231                                 self.showlist(LISTE,self.LSECT)
232                                 self.stdscr.move(1,0)
233                                 REFY = 1
234                         
235                         if c == curses.KEY_HOME:
236                                 self.LSECT=1
237                                 self.showlist(LISTE,self.LSECT)
238                                 self.stdscr.move(1,0)
239                                 REFY = 1
240
241
242                         if c == 10 :                            # \n (new line)
243                                 ERG = self.getresult(self.y )
244                                 self.end()
245                                 return ERG
246                         
247                         if c == 113 or c == 81:                 # "q or Q"
248                                 self.printoutnwait("Aborted by user!")
249                                 self.end()
250                                 sys.exit()
251                                 return 0
252                                 
253                         if REFY == 1:
254                                 REFY = 0
255                                 self.size()
256                                 self.stdscr.move(self.Y -1, 0)
257                                 self.stdscr.clrtoeol()
258                                 self.stdscr.addstr(self.Y -1, 0, self.showresult(self.y,DICT)[:self.X -1 ], curses.A_BOLD)
259                                 self.stdscr.move(self.y, self.x)
260                                 self.refresh()
261
262         def end(self):
263                 "Return terminal"
264                 # In the event of an error, restore the terminal
265                 # to a sane state.
266                 self.Y, self.X, self.y, self.x = 0, 0, 0, 0
267                 self.LISTPART=[]
268                 self.stdscr.keypad(0)
269                 curses.echo()
270                 curses.nocbreak()
271                 curses.endwin()
272                 #traceback.print_exc()
273
274         def input(self, promptstr):
275                 "raw_input equivalent in curses, asks for  Input and returns it"
276                 self.size()
277                 curses.echo()
278                 self.stdscr.move(0,0)
279                 self.stdscr.clear()
280                 self.stdscr.addstr(promptstr)
281                 self.refresh()
282                 INPUT=self.stdscr.getstr()
283                 curses.noecho()
284                 return INPUT
285         
286                                         
287         def printoutnwait(self, promptstr):
288                 "Print out Text, wait for key"
289                 curses.noecho()
290                 self.stdscr.move(0,0)
291                 self.stdscr.clear()
292 # The new Mutt client pauses after running the script already.  No reason
293 # to pause twice.  -Josh
294 #               self.stdscr.addstr(promptstr+"\n\n(press key)")
295 #               self.refresh()
296 #               c = self.stdscr.getch()# read Key events 
297                 
298
299
300 def listrex (str, rgx): # Return a list of all regexes matched in string
301         "Search string for all occurences of regex and return a list of them."
302         result = []
303         start = 0 # set counter to zero
304         ende =len (str) #set end position
305         suchadress = re.compile(rgx,re.LOCALE)#compile regular expression, with LOCALE
306         while 1:
307                 einzelerg = suchadress.search(str, start,ende) #create Match Object einzelerg
308                 if einzelerg == None:#until none is found
309                         break
310                 result.append(einzelerg.group()) #add to result
311                 start = einzelerg.end()
312         return result
313
314 def strrex (str): # Return first occurence of regular exp  as string
315         "Search string for first occurence of regular expression and return it"
316         muster = re.compile(r"<?[\w\b.ßüöä-]+\@[\w.-]+>?", re.LOCALE)   #compile re
317         matobj = muster.search(str)             #get Match Objekt from string 
318         if muster.search(str) == None:          #if none found
319                 return ""
320         return matobj.group()                   #return string 
321
322 def stripempty (str):#Looks for all empty charcters and replaces them with a space
323         "Looks for all empty charcters and replaces them with a space,kills trailing"
324         p = re.compile( "\s+")          #shorten
325         shrt = p.sub(" ", str)
326         q = re.compile("^\s+|\s+$")     #strip
327         strp = q.sub("", shrt)
328         return strp
329
330 def getmailadressfromstring(str):
331         "Takes str and gets the first word containing @ == mail adress"
332         StringSplit=string.split(str)
333         for i in range(len(StringSplit)):
334                 if "@" in StringSplit[i]:
335                         return StringSplit[i]
336         return None
337
338 ### main program
339
340 OCCLIST = listrex(file, '"?[\s\w\ö\ä\ü\-\ß\_.]*"?\s*<?[\w.-]+\@[\w.-]+>?')#get list, RE gets all Email adresses + prepending words
341
342 if OCCLIST:
343         print len(OCCLIST),"possible adresses found!." 
344 else: 
345         print"ERROR, no mails found"
346         sys.exit()
347
348
349 for i in range(len(OCCLIST)):                   #strip all whitespaces + trailing from each list member
350         OCCLIST[i] = string.strip(OCCLIST [i])
351
352
353 OCCDIC={}                                               # Dictionary created to avoid duplicates
354 for i in range(len(OCCLIST)):                   # iterate over 
355         d = OCCLIST[i]
356         Mail = getmailadressfromstring(OCCLIST[i])
357                         #strrex(OCCLIST[i])                     #Mailadresse
358         Schnitt = - len(Mail)                           #cut off mail adress
359         Mail = string.replace(Mail, "<", "")#remove <>
360         Mail = string.replace(Mail, ">", "")
361         Name = string.replace (stripempty (d[:Schnitt]), '"', '')               #leaves name
362         if not OCCDIC.get(Mail):                        # if new Emailadress
363                 Liste = []                                              # create list for names
364                 Liste.append(Name)                              # append name 
365                 OCCDIC[Mail] = Liste                    # assign list to adress
366         else :  
367                 Liste = OCCDIC[Mail]                    # otherwise get list
368                 Liste.append(Name)                              # append name to list of names
369                 OCCDIC[Mail] =  Liste                   # and assign
370
371
372 KEYS = OCCDIC.keys()                            #iterate over dictionary, sort names 
373                                                                         #KEYS are all the adresses
374
375 for z in range( len(KEYS) ): 
376         NAMLIST = OCCDIC[KEYS[z]]               # Get list of possible names
377         d = {}                                                  # sort out duplicates and 
378                                                                         # remove bad names + empty strings from adresses
379         for x in NAMLIST: 
380                 if x in ["", "<"]: continue
381                 d[x]=x
382         NAMLIST = d.values()
383         NAMLIST.sort()                                  # sort namelist alphabetically
384         print z, KEYS[z], "had possible names:", NAMLIST # Debugging output
385         OCCDIC[KEYS[z]] = NAMLIST               # 
386
387 print "\n"
388
389 ###sorting
390
391 def Comparelength(x, y):
392         "Compare number of names in OCCDIC, if equal sort alphabetically."
393         if len(OCCDIC[y]) == len(OCCDIC[x]):
394                 return cmp(x, y)
395         if len(OCCDIC[y]) < len(OCCDIC[x]):
396                 return -1
397         else:
398                 return 1        
399
400 KEYS.sort(Comparelength)                                        # Keys sort
401
402 ###menu
403
404 ScreenObject=screenC()                  # initialize curses menu
405 try:
406         ZIELADRESS = ScreenObject.menucall(KEYS, OCCDIC, "Choose adress to alias")
407         if OCCDIC[ZIELADRESS]:
408                 LISTNAM=["***ENTER own NAME"]           #add option to edit name
409                 LISTNAM= LISTNAM + OCCDIC[ZIELADRESS]
410                 ZIELNAME   = ScreenObject.menucall(LISTNAM, {}, ZIELADRESS + " has which of the possible names?")
411                 # empty Dictionary {} means show list member itself, not looked up result
412         else : ZIELNAME=""
413 except:
414         T=ScreenObject.size()
415         ScreenObject.end()
416 #       traceback.print_exc() # Uncomment for curses debugging info
417 #       print T
418         sys.exit()
419
420 ### enter new names/aliases
421
422 if  ZIELNAME == "***ENTER own NAME" or ZIELNAME == "":
423         ZIELNAME = ScreenObject.input(`ZIELADRESS` + " = " + `OCCDIC[ZIELADRESS]` + "\n" + `ZIELADRESS` + " gets which name? ")
424
425 if ZIELNAME == "":
426         ZIELNAME = "No name"
427
428 WRITEALIAS = "\n[]\nname=" + ZIELNAME + "\nemail=" + ZIELADRESS + "\n\n"
429
430 f = open(ABOOKFILE, "a")
431 f.write(WRITEALIAS)
432 f.close()
433
434 ScreenObject.printoutnwait("Item was added to "+  ABOOKFILE + "\nProgam terminated")
435 ScreenObject.end()
436