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