[fritzcall] FIX issues with FW 05.50 and swithc to csv based call list
[enigma2-plugins.git] / fritzcall / src / FritzCallFBF.py
1 # -*- coding: utf-8 -*-
2 '''
3 Created on 30.09.2012
4 $Author: michael $
5 $Revision: 733 $
6 $Date: 2012-12-21 16:43:32 +0100 (Fr, 21 Dez 2012) $
7 $Id: FritzCallFBF.py 733 2012-12-21 15:43:32Z michael $
8 '''
9
10 from . import _, debug #@UnresolvedImport # pylint: disable=E0611,F0401
11 from plugin import config, fritzbox, stripCbCPrefix, resolveNumberWithAvon, FBF_IN_CALLS, FBF_OUT_CALLS, FBF_MISSED_CALLS
12 from Tools import Notifications
13 from Screens.MessageBox import MessageBox
14 from twisted.web.client import getPage #@UnresolvedImport
15 from nrzuname import html2unicode
16
17 from urllib import urlencode 
18 import re, time, hashlib
19
20 FBF_boxInfo = 0
21 FBF_upTime = 1
22 FBF_ipAddress = 2
23 FBF_wlanState = 3
24 FBF_dslState = 4
25 FBF_tamActive = 5
26 FBF_dectActive = 6
27 FBF_faxActive = 7
28 FBF_rufumlActive = 8
29
30 # scramble text
31 def x(text, front=True):
32         if len(text) > 5:
33                 if front:
34                         return '.....' + text[5:]
35                 else:
36                         return text[:-5] + '.....'
37         else:
38                 return '.....' 
39
40 def resolveNumber(number, default=None, phonebook=None):
41         if number.isdigit():
42                 if config.plugins.FritzCall.internal.value and len(number) > 3 and number[0] == "0":
43                         number = number[1:]
44                 # strip CbC prefix
45                 number = stripCbCPrefix(number, config.plugins.FritzCall.country.value)
46                 if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
47                         number = config.plugins.FritzCall.prefix.value + number
48                 name = None
49                 if phonebook:
50                         name = phonebook.search(number)
51                 if name:
52                         #===========================================================
53                         # found = re.match('(.*?)\n.*', name)
54                         # if found:
55                         #       name = found.group(1)
56                         #===========================================================
57                         end = name.find('\n')
58                         if end != -1:
59                                 name = name[:end]
60                         number = name
61                 elif default:
62                         number = default
63                 else:
64                         name = resolveNumberWithAvon(number, config.plugins.FritzCall.country.value)
65                         if name:
66                                 number = number + ' ' + name
67         elif number == "":
68                 number = _("UNKNOWN")
69         # if len(number) > 20: number = number[:20]
70         return number
71
72 def cleanNumber(number):
73         number = number.replace('(','').replace(')','').replace(' ','').replace('-','')
74         if number[0] == '+':
75                 number = '00' + number[1:]
76         if number.startswith(config.plugins.FritzCall.country.value):
77                 number = '0' + number[len(config.plugins.FritzCall.country.value):]
78         return number
79                 
80 class FritzCallFBF:
81         def __init__(self):
82                 debug("[FritzCallFBF] __init__")
83                 self._callScreen = None
84                 self._md5LoginTimestamp = None
85                 self._md5Sid = '0000000000000000'
86                 self._callTimestamp = 0
87                 self._callList = []
88                 self._callType = config.plugins.FritzCall.fbfCalls.value
89                 self.info = None # (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive)
90                 self.getInfo(None)
91                 self.blacklist = ([], [])
92                 self.readBlacklist()
93                 self.phonebook = None
94
95         def _notify(self, text):
96                 debug("[FritzCallFBF] notify: " + text)
97                 self._md5LoginTimestamp = None
98                 if self._callScreen:
99                         debug("[FritzCallFBF] notify: try to close callScreen")
100                         self._callScreen.close()
101                         self._callScreen = None
102                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
103                         
104         def _login(self, callback=None):
105                 debug("[FritzCallFBF] _login")
106                 if self._callScreen:
107                         self._callScreen.updateStatus(_("login"))
108                 if self._md5LoginTimestamp and ((time.time() - self._md5LoginTimestamp) < float(9.5*60)) and self._md5Sid != '0000000000000000': # new login after 9.5 minutes inactivity 
109                         debug("[FritzCallFBF] _login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
110                         self._md5LoginTimestamp = time.time()
111                         callback(None)
112                 else:
113                         debug("[FritzCallFBF] _login: not logged in or outdated login")
114                         # http://fritz.box/cgi-bin/webcm?getpage=../html/login_sid.xml
115                         parms = urlencode({'getpage':'../html/login_sid.xml'})
116                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
117                         debug("[FritzCallFBF] _login: '" + url + "' parms: '" + parms + "'")
118                         getPage(url,
119                                 method="POST",
120                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
121                                                 }, postdata=parms).addCallback(lambda x: self._md5Login(callback,x)).addErrback(lambda x:self._oldLogin(callback,x))
122
123         def _oldLogin(self, callback, error): 
124                 debug("[FritzCallFBF] _oldLogin: " + repr(error))
125                 self._md5LoginTimestamp = None
126                 if config.plugins.FritzCall.password.value != "":
127                         parms = "login:command/password=%s" % (config.plugins.FritzCall.password.value)
128                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
129                         debug("[FritzCallFBF] _oldLogin: '" + url + "' parms: '" + parms + "'")
130                         getPage(url,
131                                 method="POST",
132                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
133                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
134                                                 }, postdata=parms).addCallback(self._gotPageLogin).addCallback(callback).addErrback(self._errorLogin)
135                 elif callback:
136                         debug("[FritzCallFBF] _oldLogin: no password, calling " + repr(callback))
137                         callback(None)
138
139         def _md5Login(self, callback, sidXml):
140                 def buildResponse(challenge, text):
141                         debug("[FritzCallFBF] _md5Login7buildResponse: challenge: " + challenge + ' text: ' + text)
142                         text = (challenge + '-' + text).decode('utf-8','ignore').encode('utf-16-le')
143                         for i in range(len(text)):
144                                 if ord(text[i]) > 255:
145                                         text[i] = '.'
146                         md5 = hashlib.md5()
147                         md5.update(text)
148                         debug("[FritzCallFBF] md5Login/buildResponse: " + md5.hexdigest())
149                         return challenge + '-' + md5.hexdigest()
150
151                 debug("[FritzCallFBF] _md5Login")
152                 found = re.match('.*<SID>([^<]*)</SID>', sidXml, re.S)
153                 if found:
154                         self._md5Sid = found.group(1)
155                         debug("[FritzCallFBF] _md5Login: SID "+ self._md5Sid)
156                 else:
157                         debug("[FritzCallFBF] _md5Login: no sid! That must be an old firmware.")
158                         self._oldLogin(callback, 'No error')
159                         return
160
161                 debug("[FritzCallFBF] _md5Login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
162                 self._md5LoginTimestamp = time.time()
163                 if sidXml.find('<iswriteaccess>0</iswriteaccess>') != -1:
164                         debug("[FritzCallFBF] _md5Login: logging in")
165                         found = re.match('.*<Challenge>([^<]*)</Challenge>', sidXml, re.S)
166                         if found:
167                                 challenge = found.group(1)
168                                 debug("[FritzCallFBF] _md5Login: challenge " + challenge)
169                         else:
170                                 challenge = None
171                                 debug("[FritzCallFBF] _md5Login: login necessary and no challenge! That is terribly wrong.")
172                         parms = urlencode({
173                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
174                                                         'login:command/response': buildResponse(challenge, config.plugins.FritzCall.password.value),
175                                                         })
176                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
177                         debug("[FritzCallFBF] _md5Login: '" + url + "' parms: '" + parms + "'")
178                         getPage(url,
179                                 method="POST",
180                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
181                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
182                                                 }, postdata=parms).addCallback(self._gotPageLogin).addCallback(callback).addErrback(self._errorLogin)
183                 elif callback: # we assume value 1 here, no login necessary
184                         debug("[FritzCallFBF] _md5Login: no login necessary")
185                         callback(None)
186
187         def _gotPageLogin(self, html):
188                 if self._callScreen:
189                         self._callScreen.updateStatus(_("login verification"))
190                 debug("[FritzCallFBF] _gotPageLogin: verify login")
191                 start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
192                 if start != -1:
193                         start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
194                         text = _("FRITZ!Box - Error logging in\n\n") + html[start : html.find('</p>', start)]
195                         self._notify(text)
196                 else:
197                         if self._callScreen:
198                                 self._callScreen.updateStatus(_("login ok"))
199
200                 found = re.match('.*<input type="hidden" name="sid" value="([^\"]*)"', html, re.S)
201                 if found:
202                         self._md5Sid = found.group(1)
203                         debug("[FritzCallFBF] _gotPageLogin: found sid: " + self._md5Sid)
204
205         def _errorLogin(self, error):
206                 global fritzbox
207                 debug("[FritzCallFBF] _errorLogin: %s" % (error))
208                 text = _("FRITZ!Box - Error logging in: %s\nDisabling plugin.") % error.getErrorMessage()
209                 # config.plugins.FritzCall.enable.value = False
210                 fritzbox = None
211                 self._notify(text)
212
213         def _logout(self):
214                 if self._md5LoginTimestamp:
215                         self._md5LoginTimestamp = None
216                         parms = urlencode({
217                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
218                                                         'login:command/logout':'bye bye Fritz'
219                                                         })
220                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
221                         debug("[FritzCallFBF] logout: '" + url + "' parms: '" + parms + "'")
222                         getPage(url,
223                                 method="POST",
224                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
225                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
226                                                 }, postdata=parms).addErrback(self._errorLogout)
227
228         def _errorLogout(self, error):
229                 debug("[FritzCallFBF] _errorLogout: %s" % (error))
230                 text = _("FRITZ!Box - Error logging out: %s") % error.getErrorMessage()
231                 self._notify(text)
232
233         def loadFritzBoxPhonebook(self, phonebook):
234                 debug("[FritzCallFBF] loadFritzBoxPhonebook")
235                 if config.plugins.FritzCall.fritzphonebook.value:
236                         self.phonebook = phonebook
237                         self._phoneBookID = '0'
238                         debug("[FritzCallFBF] loadFritzBoxPhonebook: logging in")
239                         self._login(self._loadFritzBoxPhonebook)
240
241         def _loadFritzBoxPhonebook(self, html):
242                 if html:
243                         #===================================================================
244                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
245                         # if found:
246                         #       self._errorLoad('Login: ' + found.group(1))
247                         #       return
248                         #===================================================================
249                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
250                         if start != -1:
251                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
252                                 self._notify('Login: ' + html[start, html.find('</p>', start)])
253                                 return
254                 parms = urlencode({
255                                                 'getpage':'../html/de/menus/menu2.html',
256                                                 'var:lang':'de',
257                                                 'var:pagename':'fonbuch',
258                                                 'var:menu':'fon',
259                                                 'sid':self._md5Sid,
260                                                 'telcfg:settings/Phonebook/Books/Select':self._phoneBookID, # this selects always the first phonbook first
261                                                 })
262                 url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
263                 debug("[FritzCallFBF] _loadFritzBoxPhonebook: '" + url + "' parms: '" + parms + "'")
264                 getPage(url,
265                         method="POST",
266                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
267                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
268                                         }, postdata=parms).addCallback(self._parseFritzBoxPhonebook).addErrback(self._errorLoad)
269
270         def _parseFritzBoxPhonebook(self, html):
271
272                 # debug("[FritzCallFBF] _parseFritzBoxPhonebook")
273
274                 # first, let us get the charset
275                 found = re.match('.*<meta http-equiv=content-type content="text/html; charset=([^"]*)">', html, re.S)
276                 if found:
277                         charset = found.group(1)
278                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: found charset: " + charset)
279                         html = html2unicode(html.replace(chr(0xf6),'').decode(charset)).encode('utf-8')
280                 else: # this is kind of emergency conversion...
281                         try:
282                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: try charset utf-8")
283                                 charset = 'utf-8'
284                                 html = html2unicode(html.decode('utf-8')).encode('utf-8') # this looks silly, but has to be
285                         except UnicodeDecodeError:
286                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: try charset iso-8859-1")
287                                 charset = 'iso-8859-1'
288                                 html = html2unicode(html.decode('iso-8859-1')).encode('utf-8') # this looks silly, but has to be
289
290                 # if re.search('document.write\(TrFon1\(\)', html):
291                 if html.find('document.write(TrFon1()') != -1:
292                         #===============================================================================
293                         #                                New Style: 7270 (FW 54.04.58, 54.04.63-11941, 54.04.70, 54.04.74-14371, 54.04.76, PHONE Labor 54.04.80-16624)
294                         #                                                       7170 (FW 29.04.70) 22.03.2009
295                         #                                                       7141 (FW 40.04.68) 22.03.2009
296                         #  We expect one line with
297                         #   TrFonName(Entry umber, Name, ???, Path to picture)
298                         #  followed by several lines with
299                         #       TrFonNr(Type,Number,Shortcut,Vanity), which all belong to the name in TrFonName.
300                         # 
301                         #  Photo could be fetched with http://192.168.0.1/lua/photo.lua?photo=<Path to picture[7:]&sid=????
302                         #===============================================================================
303                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: discovered newer firmware")
304                         found = re.match('.*<input type="hidden" name="telcfg:settings/Phonebook/Books/Name\d+" value="[Dd]reambox" id="uiPostPhonebookName\d+" disabled>\s*<input type="hidden" name="telcfg:settings/Phonebook/Books/Id\d+" value="(\d+)" id="uiPostPhonebookId\d+" disabled>', html, re.S)
305                         if found:
306                                 phoneBookID = found.group(1)
307                                 debug("[FritzCallFBF] _parseFritzBoxPhonebook: found dreambox phonebook with id: " + phoneBookID)
308                                 if self._phoneBookID != phoneBookID:
309                                         self._phoneBookID = phoneBookID
310                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: reload phonebook")
311                                         self._loadFritzBoxPhonebook(None) # reload with dreambox phonebook
312                                         return
313
314                         entrymask = re.compile('(TrFonName\("[^"]+", "[^"]+", "[^"]*"(?:, "[^"]*")?\);.*?)document.write\(TrFon1\(\)', re.S)
315                         entries = entrymask.finditer(html)
316                         for entry in entries:
317                                 # TrFonName (id, name, category)
318                                 # TODO: replace re.match?
319                                 found = re.match('TrFonName\("[^"]*", "([^"]+)", "[^"]*"(?:, "[^"]*")?\);', entry.group(1))
320                                 if found:
321                                         # debug("[FritzCallFBF] _parseFritzBoxPhonebook: name: %s" %found.group(1))
322                                         name = found.group(1).replace(',','').strip()
323                                 else:
324                                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: could not find name")
325                                         continue
326                                 # TrFonNr (type, rufnr, code, vanity)
327                                 detailmask = re.compile('TrFonNr\("([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\);', re.S)
328                                 details = detailmask.finditer(entry.group(1))
329                                 for found in details:
330                                         thisnumber = found.group(2).strip()
331                                         if not thisnumber:
332                                                 debug("[FritzCallFBF] Ignoring entry with empty number for '''%s'''" % (name))
333                                                 continue
334                                         else:
335                                                 thisname = name
336                                                 callType = found.group(1)
337                                                 if config.plugins.FritzCall.showType.value:
338                                                         if callType == "mobile":
339                                                                 thisname = thisname + " (" + _("mobile") + ")"
340                                                         elif callType == "home":
341                                                                 thisname = thisname + " (" + _("home") + ")"
342                                                         elif callType == "work":
343                                                                 thisname = thisname + " (" + _("work") + ")"
344
345                                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
346                                                         thisname = thisname + ", " + _("Shortcut") + ": " + found.group(3)
347                                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
348                                                         thisname = thisname + ", " + _("Vanity") + ": " + found.group(4)
349
350                                                 thisnumber = cleanNumber(thisnumber)
351                                                 # Beware: strings in phonebook.phonebook have to be in utf-8!
352                                                 if not self.phonebook.phonebook.has_key(thisnumber):
353                                                         # debug("[FritzCallFBF] Adding '''%s''' with '''%s'''" % (thisname.strip(), thisnumber))
354                                                         self.phonebook.phonebook[thisnumber] = thisname
355                                                 else:
356                                                         pass
357                                                         # debug("[FritzCallFBF] Ignoring '''%s''' with '''%s'''" % (thisname.strip(), thisnumber))
358
359                 # elif re.search('document.write\(TrFon\(', html):
360                 elif html.find('document.write(TrFon(') != -1:
361                         #===============================================================================
362                         #                               Old Style: 7050 (FW 14.04.33)
363                         #       We expect one line with TrFon(No,Name,Number,Shortcut,Vanity)
364                         #   Encoding should be plain Ascii...
365                         #===============================================================================                                
366                         entrymask = re.compile('TrFon\("[^"]*", "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\)', re.S)
367                         entries = entrymask.finditer(html)
368                         for found in entries:
369                                 name = found.group(1).strip().replace(',','')
370                                 # debug("[FritzCallFBF] pos: %s name: %s" %(found.group(0),name))
371                                 thisnumber = found.group(2).strip()
372                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
373                                         name = name + ", " + _("Shortcut") + ": " + found.group(3)
374                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
375                                         name = name + ", " + _("Vanity") + ": " + found.group(4)
376                                 if thisnumber:
377                                         # name = name.encode('utf-8')
378                                         # Beware: strings in phonebook.phonebook have to be in utf-8!
379                                         if not self.phonebook.phonebook.has_key(thisnumber):
380                                                 debug("[FritzCallFBF] Adding '''%s''' with '''%s'''" % (name, thisnumber))
381                                                 self.phonebook.phonebook[thisnumber] = name
382                                         else:
383                                                 debug("[FritzCallFBF] Ignoring '''%s''' with '''%s'''" % (name, thisnumber))
384                                 else:
385                                         debug("[FritzCallFBF] ignoring empty number for %s" % name)
386                                 continue
387                 elif self._md5Sid == '0000000000000000': # retry, it could be a race condition
388                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: retry loading phonebook")
389                         self.loadFritzBoxPhonebook(self.phonebook)
390                 else:
391                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: could not read FBF phonebook; wrong version?")
392                         self._notify(_("Could not read FRITZ!Box phonebook; wrong version?"))
393
394         def _errorLoad(self, error):
395                 debug("[FritzCallFBF] _errorLoad: %s" % (error))
396                 text = _("FRITZ!Box - Could not load phonebook: %s") % error.getErrorMessage()
397                 self._notify(text)
398
399         def getCalls(self, callScreen, callback, callType):
400                 #
401                 # call sequence must be:
402                 # - login
403                 # - getPage -> _gotPageLogin
404                 # - loginCallback (_getCalls)
405                 # - getPage -> _getCalls1
406                 debug("[FritzCallFBF] getCalls")
407                 self._callScreen = callScreen
408                 self._callType = callType
409                 if (time.time() - self._callTimestamp) > 180: 
410                         debug("[FritzCallFBF] getCalls: outdated data, login and get new ones: " + time.ctime(self._callTimestamp) + " time: " + time.ctime())
411                         self._callTimestamp = time.time()
412                         self._login(lambda x:self._getCalls(callback, x))
413                 elif not self._callList:
414                         debug("[FritzCallFBF] getCalls: time is ok, but no callList")
415                         self._getCalls1(callback)
416                 else:
417                         debug("[FritzCallFBF] getCalls: time is ok, callList is ok")
418                         self._gotPageCalls(callback)
419
420         def _getCalls(self, callback, html):
421                 if html:
422                         #===================================================================
423                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
424                         # if found:
425                         #       self._errorCalls('Login: ' + found.group(1))
426                         #       return
427                         #===================================================================
428                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
429                         if start != -1:
430                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
431                                 self._notify('Login: ' + html[start, html.find('</p>', start)])
432                                 return
433                 #
434                 # we need this to fill Anrufliste.csv
435                 # http://repeater1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=foncalls
436                 #
437                 debug("[FritzCallFBF] _getCalls")
438                 if html:
439                         #===================================================================
440                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
441                         # if found:
442                         #       text = _("FRITZ!Box - Error logging in: %s") + found.group(1)
443                         #       self._notify(text)
444                         #       return
445                         #===================================================================
446                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
447                         if start != -1:
448                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
449                                 self._notify(_("FRITZ!Box - Error logging in: %s") + html[start, html.find('</p>', start)])
450                                 return
451
452                 if self._callScreen:
453                         self._callScreen.updateStatus(_("preparing"))
454                 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de', 'var:pagename':'foncalls', 'var:menu':'fon', 'sid':self._md5Sid})
455                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
456                 getPage(url).addCallback(lambda x:self._getCalls1(callback)).addErrback(self._errorCalls) #@UnusedVariable # pylint: disable=W0613
457
458         def _getCalls1(self, callback):
459                 #
460                 # finally we should have successfully lgged in and filled the csv
461                 #
462                 debug("[FritzCallFBF] _getCalls1")
463                 if self._callScreen:
464                         self._callScreen.updateStatus(_("finishing"))
465                 parms = urlencode({'getpage':'../html/de/FRITZ!Box_Anrufliste.csv', 'sid':self._md5Sid})
466                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
467                 getPage(url).addCallback(lambda x:self._gotPageCalls(callback, x)).addErrback(self._errorCalls)
468
469         def _gotPageCalls(self, callback, csv=""):
470
471                 if csv:
472                         debug("[FritzCallFBF] _gotPageCalls: got csv, setting callList")
473                         if self._callScreen:
474                                 self._callScreen.updateStatus(_("done"))
475                         if csv.find('Melden Sie sich mit dem Kennwort der FRITZ!Box an') != -1:
476                                 text = _("You need to set the password of the FRITZ!Box\nin the configuration dialog to display calls\n\nIt could be a communication issue, just try again.")
477                                 # self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
478                                 self._notify(text)
479                                 return
480
481                         csv = csv.decode('iso-8859-1', 'replace').encode('utf-8', 'replace')
482                         lines = csv.splitlines()
483                         self._callList = lines
484                 elif self._callList:
485                         debug("[FritzCallFBF] _gotPageCalls: got no csv, but have callList")
486                         if self._callScreen:
487                                 self._callScreen.updateStatus(_("done, using last list"))
488                         lines = self._callList
489                 else:
490                         debug("[FritzCallFBF] _gotPageCalls: Could not get call list; wrong version?")
491                         self._notify(_("Could not get call list; wrong version?"))
492                         return
493                         
494                 callListL = []
495                 if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
496                         filtermsns = map(lambda x: x.strip(), config.plugins.FritzCall.filtermsn.value.split(","))
497                         debug("[FritzCallFBF] _gotPageCalls: filtermsns %s" % (repr(filtermsns)))
498
499                 # Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer
500                 # 0  ;1    ;2   ;3                ;4              ;5                       ;6
501                 lines = map(lambda line: line.split(';'), lines)
502                 lines = filter(lambda line: (len(line)==7 and (line[0]=="Typ" or self._callType == '.' or line[0] == self._callType)), lines)
503
504                 for line in lines:
505                         # debug("[FritzCallFBF] _gotPageCalls: line %s" % (line))
506                         direct = line[0]
507                         date = line[1]
508                         length = line[6]
509                         if config.plugins.FritzCall.phonebook.value and line[2]:
510                                 remote = resolveNumber(line[3], line[2] + " (FBF)", self.phonebook)
511                         else:
512                                 remote = resolveNumber(line[3], line[2], self.phonebook)
513                         here = line[5]
514                         start = here.find('Internet: ')
515                         if start != -1:
516                                 start += len('Internet: ')
517                                 here = here[start:]
518                         else:
519                                 here = line[5]
520                         if direct != "Typ" and config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
521                                 # debug("[FritzCallFBF] _gotPageCalls: check %s" % (here))
522                                 if here not in filtermsns:
523                                         # debug("[FritzCallFBF] _gotPageCalls: skip %s" % (here))
524                                         continue
525                         here = resolveNumber(here, line[4], self.phonebook)
526
527                         number = stripCbCPrefix(line[3], config.plugins.FritzCall.country.value)
528                         if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
529                                 number = config.plugins.FritzCall.prefix.value + number
530                         callListL.append((number, date, direct, remote, length, here))
531
532                 if callback:
533                         # debug("[FritzCallFBF] _gotPageCalls call callback with\n" + text
534                         callback(callListL)
535                 self._callScreen = None
536
537         def _errorCalls(self, error):
538                 debug("[FritzCallFBF] _errorCalls: %s" % (error))
539                 text = _("FRITZ!Box - Could not load calls: %s") % error.getErrorMessage()
540                 self._notify(text)
541
542         def dial(self, number):
543                 ''' initiate a call to number '''
544                 self._login(lambda x: self._dial(number, x))
545                 
546         def _dial(self, number, html):
547                 if html:
548                         #===================================================================
549                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
550                         # if found:
551                         #       self._errorDial('Login: ' + found.group(1))
552                         #       return
553                         #===================================================================
554                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
555                         if start != -1:
556                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
557                                 self._errorDial('Login: ' + html[start, html.find('</p>', start)])
558                                 return
559                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
560                 parms = urlencode({
561                         'getpage':'../html/de/menus/menu2.html',
562                         'var:pagename':'fonbuch',
563                         'var:menu':'home',
564                         'telcfg:settings/UseClickToDial':'1',
565                         'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
566                         'telcfg:command/Dial':number,
567                         'sid':self._md5Sid
568                         })
569                 debug("[FritzCallFBF] dial url: '" + url + "' parms: '" + parms + "'")
570                 getPage(url,
571                         method="POST",
572                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
573                         headers={
574                                         'Content-Type': "application/x-www-form-urlencoded",
575                                         'Content-Length': str(len(parms))},
576                         postdata=parms).addCallback(self._okDial).addErrback(self._errorDial)
577
578         def _okDial(self, html): #@UnusedVariable # pylint: disable=W0613
579                 debug("[FritzCallFBF] okDial")
580
581         def _errorDial(self, error):
582                 debug("[FritzCallFBF] errorDial: $s" % error)
583                 text = _("FRITZ!Box - Dialling failed: %s") % error.getErrorMessage()
584                 self._notify(text)
585
586         def changeWLAN(self, statusWLAN):
587                 ''' get status info from FBF '''
588                 debug("[FritzCallFBF] changeWLAN start")
589                 if not statusWLAN or (statusWLAN != '1' and statusWLAN != '0'):
590                         return
591                 self._login(lambda x: self._changeWLAN(statusWLAN, x))
592                 
593         def _changeWLAN(self, statusWLAN, html):
594                 if html:
595                         #===================================================================
596                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
597                         # if found:
598                         #       self._errorChangeWLAN('Login: ' + found.group(1))
599                         #       return
600                         #===================================================================
601                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
602                         if start != -1:
603                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
604                                 self._errorChangeWLAN('Login: ' + html[start, html.find('</p>', start)])
605                                 return
606                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
607                 parms = urlencode({
608                         'getpage':'../html/de/menus/menu2.html',
609                         'var:lang':'de',
610                         'var:pagename':'wlan',
611                         'var:menu':'wlan',
612                         'wlan:settings/ap_enabled':str(statusWLAN),
613                         'sid':self._md5Sid
614                         })
615                 debug("[FritzCallFBF] changeWLAN url: '" + url + "' parms: '" + parms + "'")
616                 getPage(url,
617                         method="POST",
618                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
619                         headers={
620                                         'Content-Type': "application/x-www-form-urlencoded",
621                                         'Content-Length': str(len(parms))},
622                         postdata=parms).addCallback(self._okChangeWLAN).addErrback(self._errorChangeWLAN)
623
624         def _okChangeWLAN(self, html): #@UnusedVariable # pylint: disable=W0613
625                 debug("[FritzCallFBF] _okChangeWLAN")
626
627         def _errorChangeWLAN(self, error):
628                 debug("[FritzCallFBF] _errorChangeWLAN: $s" % error)
629                 text = _("FRITZ!Box - Failed changing WLAN: %s") % error.getErrorMessage()
630                 self._notify(text)
631
632         def changeMailbox(self, whichMailbox):
633                 ''' switch mailbox on/off '''
634                 debug("[FritzCallFBF] changeMailbox start: " + str(whichMailbox))
635                 self._login(lambda x: self._changeMailbox(whichMailbox, x))
636
637         def _changeMailbox(self, whichMailbox, html):
638                 if html:
639                         #===================================================================
640                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
641                         # if found:
642                         #       self._errorChangeMailbox('Login: ' + found.group(1))
643                         #       return
644                         #===================================================================
645                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
646                         if start != -1:
647                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
648                                 self._errorChangeMailbox('Login: ' + html[start, html.find('</p>', start)])
649                                 return
650                 debug("[FritzCallFBF] _changeMailbox")
651                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
652                 if whichMailbox == -1:
653                         for i in range(5):
654                                 if self.info[FBF_tamActive][i+1]:
655                                         state = '0'
656                                 else:
657                                         state = '1'
658                                 parms = urlencode({
659                                         'tam:settings/TAM'+str(i)+'/Active':state,
660                                         'sid':self._md5Sid
661                                         })
662                                 debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
663                                 getPage(url,
664                                         method="POST",
665                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
666                                         headers={
667                                                         'Content-Type': "application/x-www-form-urlencoded",
668                                                         'Content-Length': str(len(parms))},
669                                         postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
670                 elif whichMailbox > 4:
671                         debug("[FritzCallFBF] changeMailbox invalid mailbox number")
672                 else:
673                         if self.info[FBF_tamActive][whichMailbox+1]:
674                                 state = '0'
675                         else:
676                                 state = '1'
677                         parms = urlencode({
678                                 'tam:settings/TAM'+str(whichMailbox)+'/Active':state,
679                                 'sid':self._md5Sid
680                                 })
681                         debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
682                         getPage(url,
683                                 method="POST",
684                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
685                                 headers={
686                                                 'Content-Type': "application/x-www-form-urlencoded",
687                                                 'Content-Length': str(len(parms))},
688                                 postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
689
690         def _okChangeMailbox(self, html): #@UnusedVariable # pylint: disable=W0613
691                 debug("[FritzCallFBF] _okChangeMailbox")
692
693         def _errorChangeMailbox(self, error):
694                 debug("[FritzCallFBF] _errorChangeMailbox: $s" % error)
695                 text = _("FRITZ!Box - Failed changing Mailbox: %s") % error.getErrorMessage()
696                 self._notify(text)
697
698         def getInfo(self, callback):
699                 ''' get status info from FBF '''
700                 debug("[FritzCallFBF] getInfo")
701                 self._login(lambda x:self._getInfo(callback, x))
702                 
703         def _getInfo(self, callback, html):
704                 # http://192.168.178.1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:menu=home
705                 debug("[FritzCallFBF] _getInfo: verify login")
706                 if html:
707                         #===================================================================
708                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
709                         # if found:
710                         #       self._errorGetInfo('Login: ' + found.group(1))
711                         #       return
712                         #===================================================================
713                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
714                         if start != -1:
715                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
716                                 self._errorGetInfo('Login: ' + html[start, html.find('</p>', start)])
717                                 return
718
719                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
720                 parms = urlencode({
721                         'getpage':'../html/de/menus/menu2.html',
722                         'var:lang':'de',
723                         'var:pagename':'home',
724                         'var:menu':'home',
725                         'sid':self._md5Sid
726                         })
727                 debug("[FritzCallFBF] _getInfo url: '" + url + "' parms: '" + parms + "'")
728                 getPage(url,
729                         method="POST",
730                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
731                         headers={
732                                         'Content-Type': "application/x-www-form-urlencoded",
733                                         'Content-Length': str(len(parms))},
734                         postdata=parms).addCallback(lambda x:self._okGetInfo(callback,x)).addErrback(self._errorGetInfo)
735
736         def _okGetInfo(self, callback, html):
737                 def readInfo(html):
738                         if self.info:
739                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
740                         else:
741                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = (None, None, None, None, None, None, None, None, None)
742
743                         debug("[FritzCallFBF] _okGetInfo/readinfo")
744                         found = re.match('.*<table class="tborder" id="tProdukt">\s*<tr>\s*<td style="padding-top:2px;">([^<]*)</td>\s*<td style="padding-top:2px;text-align:right;">\s*([^\s]*)\s*</td>', html, re.S)
745                         if found:
746                                 boxInfo = found.group(1)+ ', ' + found.group(2)
747                                 boxInfo = boxInfo.replace('&nbsp;',' ')
748                                 # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + boxInfo)
749                         else:
750                                 found = re.match('.*<p class="ac">([^<]*)</p>', html, re.S)
751                                 if found:
752                                         # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + found.group(1))
753                                         boxInfo = found.group(1)
754
755                         if html.find('home_coninf.txt') != -1:
756                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
757                                 parms = urlencode({
758                                         'getpage':'../html/de/home/home_coninf.txt',
759                                         'sid':self._md5Sid
760                                         })
761                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
762                                 getPage(url,
763                                         method="POST",
764                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
765                                         headers={
766                                                         'Content-Type': "application/x-www-form-urlencoded",
767                                                         'Content-Length': str(len(parms))},
768                                         postdata=parms).addCallback(lambda x:self._okSetConInfo(callback,x)).addErrback(self._errorGetInfo)
769                         else:
770                                 found = re.match('.*if \(isNaN\(jetzt\)\)\s*return "";\s*var str = "([^"]*)";', html, re.S)
771                                 if found:
772                                         # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
773                                         upTime = found.group(1)
774                                 else:
775                                         found = re.match('.*str = g_pppSeit \+"([^<]*)<br>"\+mldIpAdr;', html, re.S)
776                                         if found:
777                                                 # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
778                                                 upTime = found.group(1)
779         
780                                 found = re.match(".*IpAdrDisplay\('([.\d]+)'\)", html, re.S)
781                                 if found:
782                                         # debug("[FritzCallFBF] _okGetInfo IpAdrDisplay: " + found.group(1))
783                                         ipAddress = found.group(1)
784
785                         if html.find('g_tamActive') != -1:
786                                 entries = re.compile('if \("(\d)" == "1"\) {\s*g_tamActive \+= 1;\s*}', re.S).finditer(html)
787                                 tamActive = [0, False, False, False, False, False]
788                                 i = 1
789                                 for entry in entries:
790                                         state = entry.group(1)
791                                         if state == '1':
792                                                 tamActive[0] += 1
793                                                 tamActive[i] = True
794                                         i += 1
795                                 # debug("[FritzCallFBF] _okGetInfo tamActive: " + str(tamActive))
796                 
797                         if html.find('home_dect.txt') != -1:
798                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
799                                 parms = urlencode({
800                                         'getpage':'../html/de/home/home_dect.txt',
801                                         'sid':self._md5Sid
802                                         })
803                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
804                                 getPage(url,
805                                         method="POST",
806                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
807                                         headers={
808                                                         'Content-Type': "application/x-www-form-urlencoded",
809                                                         'Content-Length': str(len(parms))},
810                                         postdata=parms).addCallback(lambda x:self._okSetDect(callback,x)).addErrback(self._errorGetInfo)
811                         else:
812                                 if html.find('countDect2') != -1:
813                                         entries = re.compile('if \("1" == "1"\) countDect2\+\+;', re.S).findall(html)
814                                         dectActive = len(entries)
815                                         # debug("[FritzCallFBF] _okGetInfo dectActive: " + str(dectActive))
816
817                         found = re.match('.*var g_intFaxActive = "0";\s*if \("1" != ""\) {\s*g_intFaxActive = "1";\s*}\s*', html, re.S)
818                         if found:
819                                 faxActive = True
820                                 # debug("[FritzCallFBF] _okGetInfo faxActive")
821
822                         if html.find('cntRufumleitung') != -1:
823                                 entries = re.compile('mode = "1";\s*ziel = "[^"]+";\s*if \(mode == "1" \|\| ziel != ""\)\s*{\s*g_RufumleitungAktiv = true;', re.S).findall(html)
824                                 rufumlActive = len(entries)
825                                 entries = re.compile('if \("([^"]*)"=="([^"]*)"\) isAllIncoming\+\+;', re.S).finditer(html)
826                                 isAllIncoming = 0
827                                 for entry in entries:
828                                         # debug("[FritzCallFBF] _okGetInfo rufumlActive add isAllIncoming")
829                                         if entry.group(1) == entry.group(2):
830                                                 isAllIncoming += 1
831                                 if isAllIncoming == 2 and rufumlActive > 0:
832                                         rufumlActive -= 1
833                                 # debug("[FritzCallFBF] _okGetInfo rufumlActive: " + str(rufumlActive))
834
835                         # /cgi-bin/webcm?getpage=../html/de/home/home_dsl.txt
836                         # alternative through: fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:menu=internet&var:pagename=overview
837                         # { "dsl_carrier_state": "5", "umts_enabled": "0", "ata_mode": "0", "isusbgsm": "", "dsl_ds_nrate": "3130", "dsl_us_nrate": "448", "hint_dsl_no_cable": "0", "wds_enabled": "0", "wds_hop": "0", "isata": "" } 
838                         if html.find('home_dsl.txt') != -1:
839                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
840                                 parms = urlencode({
841                                         'getpage':'../html/de/home/home_dsl.txt',
842                                         'sid':self._md5Sid
843                                         })
844                                 # debug("[FritzCallFBF] get dsl state: url: '" + url + "' parms: '" + parms + "'")
845                                 getPage(url,
846                                         method="POST",
847                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
848                                         headers={
849                                                         'Content-Type': "application/x-www-form-urlencoded",
850                                                         'Content-Length': str(len(parms))},
851                                         postdata=parms).addCallback(lambda x:self._okSetDslState(callback,x)).addErrback(self._errorGetInfo)
852                         else:
853                                 found = re.match('.*function DslStateDisplay \(state\){\s*var state = "(\d+)";', html, re.S)
854                                 if found:
855                                         # debug("[FritzCallFBF] _okGetInfo DslState: " + found.group(1))
856                                         dslState = [ found.group(1), None ] # state, speed
857                                         found = re.match('.*function DslStateDisplay \(state\){\s*var state = "\d+";.*?if \("3130" != "0"\) str = "([^"]*)";', html, re.S)
858                                         if found:
859                                                 # debug("[FritzCallFBF] _okGetInfo DslSpeed: " + found.group(1).strip())
860                                                 dslState[1] = found.group(1).strip()
861                 
862                         # /cgi-bin/webcm?getpage=../html/de/home/home_wlan.txt
863                         # { "ap_enabled": "1", "active_stations": "0", "encryption": "4", "wireless_stickandsurf_enabled": "0", "is_macfilter_active": "0", "wmm_enabled": "1", "wlan_state": [ "end" ] }
864                         if html.find('home_wlan.txt') != -1:
865                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
866                                 parms = urlencode({
867                                         'getpage':'../html/de/home/home_wlan.txt',
868                                         'sid':self._md5Sid
869                                         })
870                                 # debug("[FritzCallFBF] get wlan state: url: '" + url + "' parms: '" + parms + "'")
871                                 getPage(url,
872                                         method="POST",
873                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
874                                         headers={
875                                                         'Content-Type': "application/x-www-form-urlencoded",
876                                                         'Content-Length': str(len(parms))},
877                                         postdata=parms).addCallback(lambda x:self._okSetWlanState(callback,x)).addErrback(self._errorGetInfo)
878                         else:
879                                 found = re.match('.*function WlanStateLed \(state\){.*?return StateLed\("(\d+)"\);\s*}', html, re.S)
880                                 if found:
881                                         # debug("[FritzCallFBF] _okGetInfo WlanState: " + found.group(1))
882                                         wlanState = [ found.group(1), 0, 0 ] # state, encryption, number of devices
883                                         found = re.match('.*var (?:g_)?encryption = "(\d+)";', html, re.S)
884                                         if found:
885                                                 # debug("[FritzCallFBF] _okGetInfo WlanEncrypt: " + found.group(1))
886                                                 wlanState[1] = found.group(1)
887
888                         return (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
889
890                 debug("[FritzCallFBF] _okGetInfo")
891                 info = readInfo(html)
892                 debug("[FritzCallFBF] _okGetInfo info: " + str(info))
893                 self.info = info
894                 if callback:
895                         callback(info)
896
897         def _okSetDect(self, callback, html):
898                 # debug("[FritzCallFBF] _okSetDect: " + html)
899                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
900                 if html.find('"dect_enabled": "1"') != -1:
901                         # debug("[FritzCallFBF] _okSetDect: dect_enabled")
902                         found = re.match('.*"dect_device_list":.*\[([^\]]*)\]', html, re.S)
903                         if found:
904                                 # debug("[FritzCallFBF] _okSetDect: dect_device_list: %s" %(found.group(1)))
905                                 entries = re.compile('"1"', re.S).findall(found.group(1))
906                                 dectActive = len(entries)
907                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dummy, faxActive, rufumlActive) = self.info
908                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
909                                 debug("[FritzCallFBF] _okSetDect info: " + str(self.info))
910                 if callback:
911                         callback(self.info)
912
913         def _okSetConInfo(self, callback, html):
914                 # debug("[FritzCallFBF] _okSetConInfo: " + html)
915                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
916                 found = re.match('.*"connection_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
917                 if found:
918                         # debug("[FritzCallFBF] _okSetConInfo: connection_ip: %s upTime: %s" %( found.group(1), found.group(2)))
919                         ipAddress = found.group(1)
920                         upTime = found.group(2)
921                         (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
922                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
923                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
924                 else:
925                         found = re.match('.*_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
926                         if found:
927                                 # debug("[FritzCallFBF] _okSetConInfo: _ip: %s upTime: %s" %( found.group(1), found.group(2)))
928                                 ipAddress = found.group(1)
929                                 upTime = found.group(2)
930                                 (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
931                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
932                                 debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
933                 if callback:
934                         callback(self.info)
935
936         def _okSetWlanState(self, callback, html):
937                 # debug("[FritzCallFBF] _okSetWlanState: " + html)
938                 found = re.match('.*"ap_enabled": "(\d+)"', html, re.S)
939                 if found:
940                         # debug("[FritzCallFBF] _okSetWlanState: ap_enabled: " + found.group(1))
941                         wlanState = [ found.group(1), None, None ]
942                         found = re.match('.*"encryption": "(\d+)"', html, re.S)
943                         if found:
944                                 # debug("[FritzCallFBF] _okSetWlanState: encryption: " + found.group(1))
945                                 wlanState[1] = found.group(1)
946                         found = re.match('.*"active_stations": "(\d+)"', html, re.S)
947                         if found:
948                                 # debug("[FritzCallFBF] _okSetWlanState: active_stations: " + found.group(1))
949                                 wlanState[2] = found.group(1)
950                         (boxInfo, upTime, ipAddress, dummy, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
951                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
952                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
953                 if callback:
954                         callback(self.info)
955
956         def _okSetDslState(self, callback, html):
957                 # debug("[FritzCallFBF] _okSetDslState: " + html)
958                 found = re.match('.*"dsl_carrier_state": "(\d+)"', html, re.S)
959                 if found:
960                         # debug("[FritzCallFBF] _okSetDslState: dsl_carrier_state: " + found.group(1))
961                         dslState = [ found.group(1), "" ]
962                         found = re.match('.*"dsl_ds_nrate": "(\d+)"', html, re.S)
963                         if found:
964                                 # debug("[FritzCallFBF] _okSetDslState: dsl_ds_nrate: " + found.group(1))
965                                 dslState[1] = found.group(1)
966                         found = re.match('.*"dsl_us_nrate": "(\d+)"', html, re.S)
967                         if found:
968                                 # debug("[FritzCallFBF] _okSetDslState: dsl_us_nrate: " + found.group(1))
969                                 dslState[1] = dslState[1] + '/' + found.group(1)
970                         (boxInfo, upTime, ipAddress, wlanState, dummy, tamActive, dectActive, faxActive, rufumlActive) = self.info
971                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
972                         debug("[FritzCallFBF] _okSetDslState info: " + str(self.info))
973                 if callback:
974                         callback(self.info)
975
976         def _errorGetInfo(self, error):
977                 debug("[FritzCallFBF] _errorGetInfo: %s" % (error))
978                 text = _("FRITZ!Box - Error getting status: %s") % error.getErrorMessage()
979                 self._notify(text)
980                 # linkP = open("/tmp/FritzCall_errorGetInfo.htm", "w")
981                 # linkP.write(error)
982                 # linkP.close()
983
984         def reset(self):
985                 self._login(self._reset)
986
987         def _reset(self, html):
988                 # POSTDATA=getpage=../html/reboot.html&errorpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:errorpagename=home&var:menu=home&var:pagemaster=&time:settings/time=1242207340%2C-120&var:tabReset=0&logic:command/reboot=../gateway/commands/saveconfig.html
989                 if html:
990                         #===================================================================
991                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
992                         # if found:
993                         #       self._errorReset('Login: ' + found.group(1))
994                         #       return
995                         #===================================================================
996                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
997                         if start != -1:
998                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
999                                 self._errorReset('Login: ' + html[start, html.find('</p>', start)])
1000                                 return
1001                 if self._callScreen:
1002                         self._callScreen.close()
1003                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1004                 parms = urlencode({
1005                         'getpage':'../html/reboot.html',
1006                         'var:lang':'de',
1007                         'var:pagename':'reset',
1008                         'var:menu':'system',
1009                         'logic:command/reboot':'../gateway/commands/saveconfig.html',
1010                         'sid':self._md5Sid
1011                         })
1012                 debug("[FritzCallFBF] _reset url: '" + url + "' parms: '" + parms + "'")
1013                 getPage(url,
1014                         method="POST",
1015                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1016                         headers={
1017                                         'Content-Type': "application/x-www-form-urlencoded",
1018                                         'Content-Length': str(len(parms))},
1019                         postdata=parms)
1020
1021         def _okReset(self, html): #@UnusedVariable # pylint: disable=W0613
1022                 debug("[FritzCallFBF] _okReset")
1023
1024         def _errorReset(self, error):
1025                 debug("[FritzCallFBF] _errorReset: %s" % (error))
1026                 text = _("FRITZ!Box - Error resetting: %s") % error.getErrorMessage()
1027                 self._notify(text)
1028
1029         def readBlacklist(self):
1030                 self._login(self._readBlacklist)
1031                 
1032         def _readBlacklist(self, html):
1033                 if html:
1034                         #===================================================================
1035                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1036                         # if found:
1037                         #       self._errorBlacklist('Login: ' + found.group(1))
1038                         #       return
1039                         #===================================================================
1040                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1041                         if start != -1:
1042                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1043                                 self._errorBlacklist('Login: ' + html[start, html.find('</p>', start)])
1044                                 return
1045                 # http://fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=sperre
1046                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1047                 parms = urlencode({
1048                         'getpage':'../html/de/menus/menu2.html',
1049                         'var:lang':'de',
1050                         'var:pagename':'sperre',
1051                         'var:menu':'fon',
1052                         'sid':self._md5Sid
1053                         })
1054                 debug("[FritzCallFBF] _readBlacklist url: '" + url + "' parms: '" + parms + "'")
1055                 getPage(url,
1056                         method="POST",
1057                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1058                         headers={
1059                                         'Content-Type': "application/x-www-form-urlencoded",
1060                                         'Content-Length': str(len(parms))},
1061                         postdata=parms).addCallback(self._okBlacklist).addErrback(self._errorBlacklist)
1062
1063         def _okBlacklist(self, html):
1064                 debug("[FritzCallFBF] _okBlacklist")
1065                 entries = re.compile('<script type="text/javascript">document.write\(Tr(Out|In)\("\d+", "(\d+)", "\w*"\)\);</script>', re.S).finditer(html)
1066                 self.blacklist = ([], [])
1067                 for entry in entries:
1068                         if entry.group(1) == "In":
1069                                 self.blacklist[0].append(entry.group(2))
1070                         else:
1071                                 self.blacklist[1].append(entry.group(2))
1072                 debug("[FritzCallFBF] _okBlacklist: %s" % repr(self.blacklist))
1073
1074         def _errorBlacklist(self, error):
1075                 debug("[FritzCallFBF] _errorBlacklist: %s" % (error))
1076                 text = _("FRITZ!Box - Error getting blacklist: %s") % error.getErrorMessage()
1077                 self._notify(text)
1078
1079 #===============================================================================
1080 #       def hangup(self):
1081 #               ''' hangup call on port; not used for now '''
1082 #               url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1083 #               parms = urlencode({
1084 #                       'id':'uiPostForm',
1085 #                       'name':'uiPostForm',
1086 #                       'login:command/password': config.plugins.FritzCall.password.value,
1087 #                       'telcfg:settings/UseClickToDial':'1',
1088 #                       'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
1089 #                       'telcfg:command/Hangup':'',
1090 #                       'sid':self._md5Sid
1091 #                       })
1092 #               debug("[FritzCallFBF] hangup url: '" + url + "' parms: '" + parms + "'")
1093 #               getPage(url,
1094 #                       method="POST",
1095 #                       headers={
1096 #                                       'Content-Type': "application/x-www-form-urlencoded",
1097 #                                       'Content-Length': str(len(parms))},
1098 #                       postdata=parms)
1099 #===============================================================================
1100
1101 import xml.etree.ElementTree as ET
1102 import StringIO, csv
1103
1104 class FritzCallFBF_05_50:
1105         def __init__(self):
1106                 debug("[FritzCallFBF_05_50] __init__")
1107                 self._callScreen = None
1108                 self._md5LoginTimestamp = None
1109                 self._md5Sid = '0000000000000000'
1110                 self._callTimestamp = 0
1111                 self._callList = []
1112                 self._callType = config.plugins.FritzCall.fbfCalls.value
1113                 self._phoneBookID = '0'
1114                 self._loginCallbacks = []
1115                 self.blacklist = ([], [])
1116                 self.info = None # (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive)
1117                 self.phonebook = None
1118                 self.getInfo(None)
1119                 # self.readBlacklist() now in getInfo
1120
1121         def _notify(self, text):
1122                 debug("[FritzCallFBF_05_50] notify: " + text)
1123                 self._md5LoginTimestamp = None
1124                 if self._callScreen:
1125                         debug("[FritzCallFBF_05_50] notify: try to close callScreen")
1126                         self._callScreen.close()
1127                         self._callScreen = None
1128                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1129                         
1130         def _login(self, callback=None):
1131                 debug("[FritzCallFBF_05_50] _login: " + time.ctime())
1132                 if callback:
1133                         debug("[FritzCallFBF_05_50] _login: add callback " + callback.__name__)
1134                         if self._loginCallbacks:
1135                                 # if login in process just add callback to _loginCallbacks
1136                                 self._loginCallbacks.append(callback)
1137                                 debug("[FritzCallFBF_05_50] _login: login in progress: leave")
1138                                 return
1139                         else:
1140                                 self._loginCallbacks.append(callback)
1141
1142                 if self._callScreen:
1143                         self._callScreen.updateStatus(_("login"))
1144                 if self._md5LoginTimestamp and ((time.time() - self._md5LoginTimestamp) < float(9.5*60)) and self._md5Sid != '0000000000000000': # new login after 9.5 minutes inactivity 
1145                         debug("[FritzCallFBF_05_50] _login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
1146                         self._md5LoginTimestamp = time.time()
1147                         for callback in self._loginCallbacks:
1148                                 debug("[FritzCallFBF_05_50] _login: calling " + callback.__name__)
1149                                 callback(None)
1150                         self._loginCallbacks = []
1151                 else:
1152                         debug("[FritzCallFBF_05_50] _login: not logged in or outdated login")
1153                         # http://fritz.box/login_lua.xml
1154                         url = "http://%s/login_sid.lua" % (config.plugins.FritzCall.hostname.value)
1155                         debug("[FritzCallFBF_05_50] _login: '" + url)
1156                         getPage(url,
1157                                 method="GET",
1158                                 headers={'Content-Type': "application/x-www-form-urlencoded"}
1159                                 ).addCallback(self._md5Login).addErrback(self._errorLogin)
1160
1161         def _md5Login(self, sidXml):
1162                 def buildResponse(challenge, text):
1163                         debug("[FritzCallFBF_05_50] _md5Login7buildResponse: challenge: " + challenge)
1164                         text = (challenge + '-' + text).decode('utf-8','ignore').encode('utf-16-le')
1165                         for i in range(len(text)):
1166                                 if ord(text[i]) > 255:
1167                                         text[i] = '.'
1168                         md5 = hashlib.md5()
1169                         md5.update(text)
1170                         debug("[FritzCallFBF_05_50] md5Login/buildResponse: " + md5.hexdigest())
1171                         return challenge + '-' + md5.hexdigest()
1172
1173                 linkP = open("/tmp/FritzDebug_sid.xml", "w")
1174                 linkP.write(sidXml)
1175                 linkP.close()
1176
1177                 debug("[FritzCallFBF_05_50] _md5Login")
1178                 sidX = ET.fromstring(sidXml)
1179                 self._md5Sid = sidX.find("SID").text
1180                 debug("[FritzCallFBF_05_50] _md5Login: SID "+ repr(self._md5Sid))
1181                 if self._md5Sid:
1182                         debug("[FritzCallFBF_05_50] _md5Login: SID "+ self._md5Sid)
1183                 else:
1184                         debug("[FritzCallFBF_05_50] _md5Login: no sid! That must be an old firmware.")
1185                         self._errorLogin('No sid?!?')
1186                         return
1187
1188                 debug("[FritzCallFBF_05_50] _md5Login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
1189                 self._md5LoginTimestamp = time.time()
1190                 if self._md5Sid != "0000000000000000":
1191                         for callback in self._loginCallbacks:
1192                                 debug("[FritzCallFBF_05_50] _md5Login: calling " + callback.__name__)
1193                                 callback(None)
1194                         self._loginCallbacks = []
1195                         return
1196
1197                 challenge = sidX.find("Challenge").text
1198                 if challenge:
1199                         debug("[FritzCallFBF_05_50] _md5Login: challenge " + challenge)
1200                 else:
1201                         debug("[FritzCallFBF_05_50] _md5Login: login necessary and no challenge! That is terribly wrong.")
1202
1203                 # TODO: check validity of username?
1204                 parms = urlencode({
1205                                                 'username': config.plugins.FritzCall.username.value,
1206                                                 'response': buildResponse(challenge, config.plugins.FritzCall.password.value),
1207                                                 })
1208                 url = "http://%s/login_sid.lua" % (config.plugins.FritzCall.hostname.value)
1209                 debug("[FritzCallFBF_05_50] _md5Login: " + url + "?" + parms)
1210                 getPage(url,
1211                         method="POST",
1212                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1213                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1214                                         }, postdata=parms).addCallback(self._gotPageLogin).addErrback(self._errorLogin)
1215
1216         def _gotPageLogin(self, sidXml):
1217                 if self._callScreen:
1218                         self._callScreen.updateStatus(_("login verification"))
1219                 debug("[FritzCallFBF_05_50] _gotPageLogin: verify login")
1220                 start = sidXml.find('<p class="errorMessage">FEHLER:&nbsp;')
1221                 if start != -1:
1222                         start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1223                         text = _("FRITZ!Box - Error logging in\n\n") + sidXml[start : sidXml.find('</p>', start)]
1224                         self._notify(text)
1225                         return
1226                 else:
1227                         if self._callScreen:
1228                                 self._callScreen.updateStatus(_("login ok"))
1229
1230                 sidX = ET.fromstring(sidXml)
1231                 self._md5Sid = sidX.find("SID").text
1232                 if self._md5Sid and self._md5Sid != "0000000000000000":
1233                         debug("[FritzCallFBF_05_50] _gotPageLogin: found sid: " + self._md5Sid)
1234                 else:
1235                         self._notify(_("FRITZ!Box - Error logging in\n\n") + _("no SID"))
1236                         return
1237
1238                 for callback in self._loginCallbacks:
1239                         debug("[FritzCallFBF_05_50] _gotPageLogin: calling " + callback.__name__)
1240                         callback(None)
1241                 self._loginCallbacks = []
1242
1243         def _errorLogin(self, error):
1244                 global fritzbox
1245                 if type(error).__name__ == "str":
1246                         text = error
1247                 else:
1248                         text = error.getErrorMessage()
1249                 text = _("FRITZ!Box - Error logging in: %s\nDisabling plugin.") % text
1250                 # config.plugins.FritzCall.enable.value = False
1251                 fritzbox = None
1252                 debug("[FritzCallFBF_05_50] _errorLogin: %s" % (error))
1253                 self._notify(text)
1254
1255         def _logout(self):
1256                 if self._md5LoginTimestamp:
1257                         self._md5LoginTimestamp = None
1258                         parms = urlencode({
1259                                                         'sid':self._md5Sid,
1260                                                         'logout':'bye bye Fritz'
1261                                                         })
1262                         url = "http://%s/login_sid.lua" % (config.plugins.FritzCall.hostname.value)
1263                         debug("[FritzCallFBF_05_50] logout: " + url + "?" + parms)
1264                         getPage(url,
1265                                 method="POST",
1266                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1267                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1268                                                 }, postdata=parms).addErrback(self._errorLogout)
1269
1270         def _errorLogout(self, error):
1271                 debug("[FritzCallFBF_05_50] _errorLogout: %s" % (error))
1272                 text = _("FRITZ!Box - Error logging out: %s") % error.getErrorMessage()
1273                 self._notify(text)
1274
1275         def loadFritzBoxPhonebook(self, phonebook):
1276                 self.phonebook = phonebook
1277                 self._login(self._selectFritzBoxPhonebook)
1278
1279         def _selectFritzBoxPhonebook(self, html=None):
1280                 # TODO: error check...
1281                 # look for phonebook called dreambox or Dreambox
1282                 parms = urlencode({
1283                                                 'sid':self._md5Sid,
1284                                                 })
1285                 url = "http://%s/fon_num/fonbook_select.lua" % (config.plugins.FritzCall.hostname.value)
1286                 debug("[FritzCallFBF_05_50] _selectPhonebook: " + url + "?" + parms)
1287                 getPage(url,
1288                         method="POST",
1289                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1290                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1291                                         }, postdata=parms).addCallback(self._loadFritzBoxPhonebook).addErrback(self._errorLoad)
1292
1293         def _loadFritzBoxPhonebook(self, html):
1294                 # Firmware 05.27 onwards
1295                 # look for phonebook called [dD]reambox and get bookid
1296                 found = re.match('.*<label for="uiBookid:([\d]+)">([dD]reambox|FritzCall)', html, re.S)
1297                 if found:
1298                         bookid = found.group(1)
1299                 else:
1300                         bookid = 1
1301                 debug("[FritzCallFBF_05_50] _loadFritzBoxPhonebook: phonebook %s" % (bookid))
1302
1303                 # http://192.168.178.1/fon_num/fonbook_list.lua?sid=2faec13b0000f3a2
1304                 parms = urlencode({
1305                                                 'bookid':bookid,
1306                                                 'sid':self._md5Sid,
1307                                                 })
1308                 url = "http://%s/fon_num/fonbook_list.lua" % (config.plugins.FritzCall.hostname.value)
1309                 debug("[FritzCallFBF_05_50] _loadFritzBoxPhonebookNew: " + url + "?" + parms)
1310                 getPage(url,
1311                         method="POST",
1312                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1313                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1314                                         }, postdata=parms).addCallback(self._parseFritzBoxPhonebook).addErrback(self._errorLoad)
1315
1316         def _parseFritzBoxPhonebook(self, html):
1317                 debug("[FritzCallFBF_05_50] _parseFritzBoxPhonebook")
1318                 # first, let us get the charset
1319                 found = re.match('.*<meta http-equiv=content-type content="text/html; charset=([^"]*)">', html, re.S)
1320                 if found:
1321                         charset = found.group(1)
1322                         debug("[FritzCallFBF_05_50] _parseFritzBoxPhonebook: found charset: " + charset)
1323                         html = html2unicode(html.replace(chr(0xf6),'').decode(charset)).encode('utf-8')
1324                 else: # this is kind of emergency conversion...
1325                         try:
1326                                 debug("[FritzCallFBF_05_50] _parseFritzBoxPhonebook: try charset utf-8")
1327                                 charset = 'utf-8'
1328                                 html = html2unicode(html.decode('utf-8')).encode('utf-8') # this looks silly, but has to be
1329                         except UnicodeDecodeError:
1330                                 debug("[FritzCallFBF_05_50] _parseFritzBoxPhonebook: try charset iso-8859-1")
1331                                 charset = 'iso-8859-1'
1332                                 html = html2unicode(html.decode('iso-8859-1')).encode('utf-8') # this looks silly, but has to be
1333
1334                 # cleanout hrefs
1335                 html = re.sub("<a href[^>]*>", "", html)
1336                 html = re.sub("</a>", "", html)
1337
1338                 #=======================================================================
1339                 # linkP = open("/tmp/FritzCall_Phonebook.htm", "w")
1340                 # linkP.write(html)
1341                 # linkP.close()
1342                 #=======================================================================
1343
1344                 if html.find('class="zebra_reverse"') != -1:
1345                         debug("[FritzCallFBF_05_50] Found new 7390 firmware")
1346                         entrymask = re.compile('<td class="tname" title="([^"]*)">[^<]*</td><td class="tnum">([^<]+(?:<br>[^<]+)*)</td><td class="ttype">([^<]+(?:<br>[^<]+)*)</td><td class="tcode">([^<]*(?:<br>[^<]*)*)</td><td class="tvanity">([^<]*(?:<br>[^<]*)*)</td>', re.S)
1347                         entries = entrymask.finditer(html)
1348                         for found in entries:
1349                                 # debug("[FritzCallFBF_05_50] _parseFritzBoxPhonebook: processing entry for '''%s'''" % repr(found.groups()))
1350                                 name = html2unicode(re.sub(",", "", found.group(1)))
1351                                 thisnumbers = found.group(2).split("<br>")
1352                                 thistypes = found.group(3).split("<br>")
1353                                 thiscodes = found.group(4).split("<br>")
1354                                 thisvanitys = found.group(5).split("<br>")
1355                                 for i in range(len(thisnumbers)):
1356                                         thisnumber = cleanNumber(thisnumbers[i])
1357                                         if self.phonebook.phonebook.has_key(thisnumber):
1358                                                 debug("[FritzCallFBF_05_50] Ignoring '''%s''' with '''%s'''" % (name, thisnumber))
1359                                                 continue
1360
1361                                         if not thisnumbers[i]:
1362                                                 debug("[FritzCallFBF_05_50] _parseFritzBoxPhonebook: Ignoring entry with empty number for '''%s'''" % (x(name)))
1363                                                 continue
1364                                         else:
1365                                                 thisname = name
1366                                                 if config.plugins.FritzCall.showType.value and thistypes[i]:
1367                                                         thisname = thisname + " (" + thistypes[i] + ")"
1368                                                 if config.plugins.FritzCall.showShortcut.value and thiscodes[i]:
1369                                                         thisname = thisname + ", " + _("Shortcut") + ": " + thiscodes[i]
1370                                                 if config.plugins.FritzCall.showVanity.value and thisvanitys[i]:
1371                                                         thisname = thisname + ", " + _("Vanity") + ": " + thisvanitys[i]
1372         
1373                                                 debug("[FritzCallFBF_05_50] _parseFritzBoxPhonebook: Adding '''%s''' with '''%s'''" % (x(thisname.strip()), x(thisnumber, False)))
1374                                                 # Beware: strings in phonebook.phonebook have to be in utf-8!
1375                                                 self.phonebook.phonebook[thisnumber] = thisname
1376                 else:
1377                         self._notify(_("Could not parse FRITZ!Box Phonebook entry"))
1378
1379         def _errorLoad(self, error):
1380                 debug("[FritzCallFBF_05_50] _errorLoad: %s" % (error))
1381                 text = _("FRITZ!Box - Could not load phonebook: %s") % error.getErrorMessage()
1382                 self._notify(text)
1383
1384         def getCalls(self, callScreen, callback, callType):
1385                 #
1386                 # FW 05.27 onwards
1387                 #
1388                 self._callScreen = callScreen
1389                 self._callType = callType
1390                 debug("[FritzCallFBF_05_50] _getCalls1New")
1391                 if self._callScreen:
1392                         self._callScreen.updateStatus(_("finishing"))
1393                 # http://192.168.178.1/fon_num/foncalls_list.lua?sid=da78ab0797197dc7
1394                 # TODO: besser csv mit: https://fritz.box/fon_num/foncalls_list.lua?sid=dea373c2d0257a41&csv=
1395                 parms = urlencode({'sid':self._md5Sid, 'csv':''})
1396                 url = "http://%s/fon_num/foncalls_list.lua?%s" % (config.plugins.FritzCall.hostname.value, parms)
1397                 getPage(url).addCallback(lambda x:self._gotPageCalls(callback, x)).addErrback(self._errorCalls)
1398
1399         def _gotPageCalls(self, callback, csvString=""):
1400
1401                 debug("[FritzCallFBF_05_50] _gotPageCalls")
1402                 if self._callScreen:
1403                         self._callScreen.updateStatus(_("preparing"))
1404
1405                 callListL = []
1406                 if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
1407                         filtermsns = map(lambda x: x.strip(), config.plugins.FritzCall.filtermsn.value.split(","))
1408                         debug("[FritzCallFBF_05_50] _gotPageCalls: filtermsns %s" % (repr(filtermsns)))
1409
1410                 #=======================================================================
1411                 # linkP = open("/tmp/FritzCalls.csv", "w")
1412                 # linkP.write(csvString)
1413                 # linkP.close()
1414                 #=======================================================================
1415
1416                 # 0: direct; 1: date; 2: Name; 3: Nummer; 4: Nebenstelle; 5: Eigene Rufnumme; 6: Dauer
1417                 calls = csv.reader(StringIO.StringIO(csvString), delimiter=';')
1418                 calls.next() # skip sep
1419                 for call in calls:
1420                         if len(call) != 7:
1421                                 debug("[FritzCallFBF_05_50] _gotPageCalls: skip %s len: %s" %(repr(call), str(len(call))))
1422                                 continue
1423                         direct = call[0]
1424                         if direct == '1':
1425                                 direct = FBF_IN_CALLS
1426                         elif direct == '4':
1427                                 direct = FBF_OUT_CALLS
1428                         elif direct == '2':
1429                                 direct = FBF_MISSED_CALLS
1430                         date = call[1]
1431                         length = call[6]
1432                         number = stripCbCPrefix(call[3], config.plugins.FritzCall.country.value)
1433                         if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
1434                                 number = config.plugins.FritzCall.prefix.value + number
1435                         # debug("[FritzCallFBF_05_50] _gotPageCalls: number: " + number)
1436                         remote = resolveNumber(number, call[2], self.phonebook)
1437                         # debug("[FritzCallFBF_05_50] _gotPageCalls: remote. " + remote)
1438
1439                         here = call[5]
1440                         if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
1441                                 # debug("[FritzCallFBF_05_50] _gotPageCalls: check %s" % (here))
1442                                 if here not in filtermsns:
1443                                         # debug("[FritzCallFBF_05_50] _gotPageCalls: skip %s" % (here))
1444                                         continue
1445                         here = resolveNumber(here, call[4], self.phonebook)
1446                         # debug("[FritzCallFBF_05_50] _gotPageCalls: here: " + here)
1447
1448                         debug("[FritzCallFBF_05_50] _gotPageCalls: append: %s" % repr((x(number, False), date, direct, x(remote), length, x(here))))
1449                         callListL.append((number, date, direct, remote, length, here))
1450
1451                 if callback:
1452                         # debug("[FritzCallFBF_05_50] _gotPageCalls call callback with\n" + text
1453                         callback(callListL)
1454                 self._callScreen = None
1455
1456         def _errorCalls(self, error):
1457                 debug("[FritzCallFBF_05_50] _errorCalls: %s" % (error))
1458                 text = _("FRITZ!Box - Could not load calls: %s") % error.getErrorMessage()
1459                 self._notify(text)
1460
1461         def dial(self, number):
1462                 ''' initiate a call to number '''
1463                 self._login(lambda x: self._dial(number, x))
1464                 
1465         def _dial(self, number, html):
1466                 if html:
1467                         #===================================================================
1468                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1469                         # if found:
1470                         #       self._errorDial('Login: ' + found.group(1))
1471                         #       return
1472                         #===================================================================
1473                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1474                         if start != -1:
1475                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1476                                 self._errorDial('Login: ' + html[start, html.find('</p>', start)])
1477                                 return
1478                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1479                 parms = urlencode({
1480                         'getpage':'../html/de/menus/menu2.html',
1481                         'var:pagename':'fonbuch',
1482                         'var:menu':'home',
1483                         'telcfg:settings/UseClickToDial':'1',
1484                         'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
1485                         'telcfg:command/Dial':number,
1486                         'sid':self._md5Sid
1487                         })
1488                 debug("[FritzCallFBF_05_50] dial url: " + url + "?" + parms)
1489                 getPage(url,
1490                         method="POST",
1491                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1492                         headers={
1493                                         'Content-Type': "application/x-www-form-urlencoded",
1494                                         'Content-Length': str(len(parms))},
1495                         postdata=parms).addCallback(self._okDial).addErrback(self._errorDial)
1496
1497         def _okDial(self, html): #@UnusedVariable # pylint: disable=W0613
1498                 debug("[FritzCallFBF_05_50] okDial")
1499
1500         def _errorDial(self, error):
1501                 debug("[FritzCallFBF_05_50] errorDial: $s" % error)
1502                 text = _("FRITZ!Box - Dialling failed: %s") % error.getErrorMessage()
1503                 self._notify(text)
1504
1505         def changeWLAN(self, statusWLAN):
1506                 ''' get status info from FBF '''
1507                 debug("[FritzCallFBF_05_50] changeWLAN start")
1508                 Notifications.AddNotification(MessageBox, _("not yet implemented"), type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1509                 return
1510
1511                 if not statusWLAN or (statusWLAN != '1' and statusWLAN != '0'):
1512                         return
1513                 self._login(lambda x: self._changeWLAN(statusWLAN, x))
1514                 
1515         def _changeWLAN(self, statusWLAN, html):
1516                 if html:
1517                         #===================================================================
1518                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1519                         # if found:
1520                         #       self._errorChangeWLAN('Login: ' + found.group(1))
1521                         #       return
1522                         #===================================================================
1523                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1524                         if start != -1:
1525                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1526                                 self._errorChangeWLAN('Login: ' + html[start, html.find('</p>', start)])
1527                                 return
1528
1529                 if statusWLAN == '0':
1530                         statusWLAN = 'off'
1531                 else:
1532                         statusWLAN = 'off'
1533
1534                 url = "http://%s//wlan/wlan_settings.lua" % config.plugins.FritzCall.hostname.value
1535                 parms = urlencode({
1536                         'active':str(statusWLAN),
1537                         'sid':self._md5Sid
1538                         })
1539                 debug("[FritzCallFBF] changeWLAN url: " + url + "?" + parms)
1540                 getPage(url,
1541                         method="POST",
1542                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1543                         headers={
1544                                         'Content-Type': "application/x-www-form-urlencoded",
1545                                         'Content-Length': str(len(parms))},
1546                         postdata=parms).addCallback(self._okChangeWLAN).addErrback(self._errorChangeWLAN)
1547
1548         def _okChangeWLAN(self, html): #@UnusedVariable # pylint: disable=W0613
1549                 debug("[FritzCallFBF] _okChangeWLAN")
1550
1551         def _errorChangeWLAN(self, error):
1552                 debug("[FritzCallFBF] _errorChangeWLAN: $s" % error)
1553                 text = _("FRITZ!Box - Failed changing WLAN: %s") % error.getErrorMessage()
1554                 self._notify(text)
1555
1556         def changeMailbox(self, whichMailbox):
1557                 ''' switch mailbox on/off '''
1558                 debug("[FritzCallFBF_05_50] changeMailbox start: " + str(whichMailbox))
1559                 Notifications.AddNotification(MessageBox, _("not yet implemented"), type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1560
1561         def _changeMailbox(self, whichMailbox, html):
1562                 return
1563
1564         def _okChangeMailbox(self, html): #@UnusedVariable # pylint: disable=W0613
1565                 debug("[FritzCallFBF_05_50] _okChangeMailbox")
1566
1567         def _errorChangeMailbox(self, error):
1568                 debug("[FritzCallFBF_05_50] _errorChangeMailbox: $s" % error)
1569                 text = _("FRITZ!Box - Failed changing Mailbox: %s") % error.getErrorMessage()
1570                 self._notify(text)
1571
1572         def getInfo(self, callback):
1573                 ''' get status info from FBF '''
1574                 debug("[FritzCallFBF_05_50] getInfo")
1575                 self._login(lambda x:self._getInfo(callback, x))
1576                 
1577         def _getInfo(self, callback, html):
1578                 debug("[FritzCallFBF_05_50] _getInfo: verify login")
1579                 if html:
1580                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1581                         if start != -1:
1582                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1583                                 self._errorGetInfo('Login: ' + html[start, html.find('</p>', start)])
1584                                 return
1585
1586                 self._readBlacklist()
1587
1588                 url = "http://%s/home/home.lua" % config.plugins.FritzCall.hostname.value
1589                 parms = urlencode({
1590                         'sid':self._md5Sid
1591                         })
1592                 debug("[FritzCallFBF_05_50] _getInfo url: " + url + "?" + parms)
1593                 getPage(url,
1594                         method="POST",
1595                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1596                         headers={
1597                                         'Content-Type': "application/x-www-form-urlencoded",
1598                                         'Content-Length': str(len(parms))},
1599                         postdata=parms).addCallback(lambda x:self._okGetInfo(callback,x)).addErrback(self._errorGetInfo)
1600
1601         def _okGetInfo(self, callback, html):
1602
1603                 debug("[FritzCallFBF_05_50] _okGetInfo")
1604
1605                 #=======================================================================
1606                 # linkP = open("/tmp/FritzCallInfo.htm", "w")
1607                 # linkP.write(html)
1608                 # linkP.close()
1609                 #=======================================================================
1610
1611                 if self.info:
1612                         (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
1613                 else:
1614                         (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = (None, None, None, None, None, None, None, None, None)
1615
1616                 found = re.match('.*<table id="tProdukt" class="tborder"> <tr> <td style="[^"]*" >([^<]*)</td> <td style="[^"]*" class="td_right">([^<]*)<a target="[^"]*" onclick="[^"]*" href="[^"]*">([^<]*)</a></td> ', html, re.S)
1617                 if found:
1618                         boxInfo = found.group(1) + ', ' + found.group(2) + found.group(3)
1619                         boxInfo = boxInfo.replace('&nbsp;',' ')
1620                         debug("[FritzCallFBF_05_50] _okGetInfo Boxinfo: " + boxInfo)
1621
1622                 found = re.match('.*<div id=\'ipv4_info\'><span class="[^"]*">verbunden seit ([^<]*)</span>', html, re.S)
1623                 if found:
1624                         upTime = found.group(1)
1625                         debug("[FritzCallFBF_05_50] _okGetInfo upTime: " + upTime)
1626
1627                 found = re.match('.*IP-Adresse: ([^<]*)</span>', html, re.S)
1628                 if found:
1629                         ipAddress = found.group(1)
1630                         debug("[FritzCallFBF_05_50] _okGetInfo ipAddress: " + ipAddress)
1631
1632                 # wlanstate = [ active, encrypted, no of devices ]
1633                 found = re.match('.*<tr id="uiTrWlan"><td class="(led_gray|led_green|led_red)"></td><td><a href="[^"]*">WLAN</a></td><td>(aus|an)(|, gesichert)</td>', html, re.S)
1634                 if found:
1635                         if found.group(1) == "led_green":
1636                                 if found.group(2):
1637                                         wlanState = [ '1', '1', '' ]
1638                                 else:
1639                                         wlanState = [ '1', '0', '' ]
1640                         else:
1641                                 wlanState = [ '0', '0', '0' ]
1642                         debug("[FritzCallFBF_05_50] _okGetInfo wlanState: " + repr(wlanState))
1643
1644                 found = re.match('.*<tr id="uiTrDsl"><td class="(led_gray|led_green|led_red)">', html, re.S)
1645                 if found:
1646                         if found.group(1) == "led_green":
1647                                 dslState = ['5', None, None]
1648                                 found = re.match('.*<a href="[^"]*">DSL</a></td><td >bereit, ([^<]*)<img src=\'[^\']*\' height=\'[^\']*\'>&nbsp;([^<]*)<img src=\'[^\']*\' height=\'[^\']*\'></td></tr>', html, re.S)
1649                                 if found:
1650                                         dslState[1] = found.group(1) + "/" + found.group(2)
1651                         else:
1652                                 dslState = ['0', None, None]
1653                 debug("[FritzCallFBF_05_50] _okGetInfo dslState: " + repr(dslState))
1654
1655                 found = re.match('.*<tr id="trTam" style=""><td><a href="[^"]*">Anrufbeantworter</a></td><td title=\'[^\']*\'>([\d]+) aktiv([^<]*)</td></tr>', html, re.S)
1656                 if found:
1657                         # found.group(2) could be ', neue Nachrichten vorhanden'; ignore for now
1658                         tamActive = [ found.group(1), False, False, False, False, False]
1659                 debug("[FritzCallFBF_05_50] _okGetInfo tamActive: " + repr(tamActive))
1660
1661                 found = re.match('.*<tr id="uiTrDect"><td class="led_green"></td><td><a href="[^"]*">DECT</a></td><td>an, (ein|\d*) Schnurlostelefon', html, re.S)
1662                 if found:
1663                         dectActive = found.group(1)
1664                 debug("[FritzCallFBF_05_50] _okGetInfo dectActive: " + repr(dectActive))
1665
1666                 found = re.match('.*<td>Integriertes Fax aktiv</td>', html, re.S)
1667                 if found:
1668                         faxActive = True
1669                 debug("[FritzCallFBF_05_50] _okGetInfo faxActive: " + repr(faxActive))
1670
1671                 found = re.match('.* <tr style=""><td><a href="[^"]*">Rufumleitung</a></td><td>deaktiviert</td></tr>', html, re.S)
1672                 if found:
1673                         rufumlActive = False
1674                 else:
1675                         rufumlActive = True
1676                 debug("[FritzCallFBF_05_50] _okGetInfo rufumlActive: " + repr(rufumlActive))
1677
1678                 info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
1679                 debug("[FritzCallFBF_05_50] _okGetInfo info: " + str(info))
1680                 self.info = info
1681                 if callback:
1682                         callback(info)
1683
1684         def _okSetDect(self, callback, html):
1685                 return
1686         
1687         def _okSetConInfo(self, callback, html):
1688                 return
1689
1690         def _okSetWlanState(self, callback, html):
1691                 return
1692
1693         def _okSetDslState(self, callback, html):
1694                 return
1695
1696         def _errorGetInfo(self, error):
1697                 debug("[FritzCallFBF_05_50] _errorGetInfo: %s" % (error))
1698                 text = _("FRITZ!Box - Error getting status: %s") % error.getErrorMessage()
1699                 self._notify(text)
1700                 return
1701
1702         def reset(self):
1703                 self._login(self._reset)
1704
1705         def _reset(self, html):
1706                 # POSTDATA=getpage=../html/reboot.html&errorpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:errorpagename=home&var:menu=home&var:pagemaster=&time:settings/time=1242207340%2C-120&var:tabReset=0&logic:command/reboot=../gateway/commands/saveconfig.html
1707                 if html:
1708                         #===================================================================
1709                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1710                         # if found:
1711                         #       self._errorReset('Login: ' + found.group(1))
1712                         #       return
1713                         #===================================================================
1714                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1715                         if start != -1:
1716                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1717                                 self._errorReset('Login: ' + html[start, html.find('</p>', start)])
1718                                 return
1719                 if self._callScreen:
1720                         self._callScreen.close()
1721                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1722                 parms = urlencode({
1723                         'getpage':'../html/reboot.html',
1724                         'var:lang':'de',
1725                         'var:pagename':'reset',
1726                         'var:menu':'system',
1727                         'logic:command/reboot':'../gateway/commands/saveconfig.html',
1728                         'sid':self._md5Sid
1729                         })
1730                 debug("[FritzCallFBF_05_50] _reset url: " + url + "?" + parms)
1731                 getPage(url,
1732                         method="POST",
1733                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1734                         headers={
1735                                         'Content-Type': "application/x-www-form-urlencoded",
1736                                         'Content-Length': str(len(parms))},
1737                         postdata=parms)
1738
1739         def _okReset(self, html): #@UnusedVariable # pylint: disable=W0613
1740                 debug("[FritzCallFBF_05_50] _okReset")
1741
1742         def _errorReset(self, error):
1743                 debug("[FritzCallFBF_05_50] _errorReset: %s" % (error))
1744                 text = _("FRITZ!Box - Error resetting: %s") % error.getErrorMessage()
1745                 self._notify(text)
1746
1747         def _readBlacklist(self):
1748                 # http://fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=sperre
1749                 url = "http://%s/fon_num/sperre.lua" % config.plugins.FritzCall.hostname.value
1750                 parms = urlencode({
1751                         'sid':self._md5Sid
1752                         })
1753                 debug("[FritzCallFBF_05_50] _readBlacklist url: " + url + "?" + parms)
1754                 getPage(url,
1755                         method="POST",
1756                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1757                         headers={
1758                                         'Content-Type': "application/x-www-form-urlencoded",
1759                                         'Content-Length': str(len(parms))},
1760                         postdata=parms).addCallback(self._okBlacklist).addErrback(self._errorBlacklist)
1761
1762         def _okBlacklist(self, html):
1763                 debug("[FritzCallFBF_05_50] _okBlacklist")
1764                 #=======================================================================
1765                 # linkP = open("/tmp/FritzCallBlacklist.htm", "w")
1766                 # linkP.write(html)
1767                 # linkP.close()
1768                 #=======================================================================
1769                 entries = re.compile('<span title="(?:Ankommende|Ausgehende) Rufe">(Ankommende|Ausgehende) Rufe</span></nobr></td><td><nobr><span title="[\d]+">([\d]+)</span>', re.S).finditer(html)
1770                 self.blacklist = ([], [])
1771                 for entry in entries:
1772                         if entry.group(1) == "Ankommende":
1773                                 self.blacklist[0].append(entry.group(2))
1774                         else:
1775                                 self.blacklist[1].append(entry.group(2))
1776                 debug("[FritzCallFBF_05_50] _okBlacklist: %s" % repr(self.blacklist))
1777
1778         def _errorBlacklist(self, error):
1779                 debug("[FritzCallFBF_05_50] _errorBlacklist: %s" % (error))
1780                 text = _("FRITZ!Box - Error getting blacklist: %s") % error.getErrorMessage()
1781                 self._notify(text)
1782
1783 class FritzCallFBF_05_27:
1784         def __init__(self):
1785                 debug("[FritzCallFBF_05_27] __init__")
1786                 self._callScreen = None
1787                 self._md5LoginTimestamp = None
1788                 self._md5Sid = '0000000000000000'
1789                 self._callTimestamp = 0
1790                 self._callList = []
1791                 self._callType = config.plugins.FritzCall.fbfCalls.value
1792                 self._phoneBookID = '0'
1793                 self._loginCallbacks = []
1794                 self.blacklist = ([], [])
1795                 self.info = None # (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive)
1796                 self.phonebook = None
1797                 self.getInfo(None)
1798                 # self.readBlacklist() now in getInfo
1799
1800         def _notify(self, text):
1801                 debug("[FritzCallFBF_05_27] notify: " + text)
1802                 self._md5LoginTimestamp = None
1803                 if self._callScreen:
1804                         debug("[FritzCallFBF_05_27] notify: try to close callScreen")
1805                         self._callScreen.close()
1806                         self._callScreen = None
1807                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1808                         
1809         def _login(self, callback=None):
1810                 debug("[FritzCallFBF_05_27] _login: " + time.ctime())
1811                 if callback:
1812                         debug("[FritzCallFBF_05_27] _login: add callback " + callback.__name__)
1813                         if self._loginCallbacks:
1814                                 # if login in process just add callback to _loginCallbacks
1815                                 self._loginCallbacks.append(callback)
1816                                 debug("[FritzCallFBF_05_27] _login: login in progress: leave")
1817                                 return
1818                         else:
1819                                 self._loginCallbacks.append(callback)
1820
1821                 if self._callScreen:
1822                         self._callScreen.updateStatus(_("login"))
1823                 if self._md5LoginTimestamp and ((time.time() - self._md5LoginTimestamp) < float(9.5*60)) and self._md5Sid != '0000000000000000': # new login after 9.5 minutes inactivity 
1824                         debug("[FritzCallFBF_05_27] _login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
1825                         self._md5LoginTimestamp = time.time()
1826                         for callback in self._loginCallbacks:
1827                                 debug("[FritzCallFBF_05_27] _login: calling " + callback.__name__)
1828                                 callback(None)
1829                         self._loginCallbacks = []
1830                 else:
1831                         debug("[FritzCallFBF_05_27] _login: not logged in or outdated login")
1832                         # http://fritz.box/cgi-bin/webcm?getpage=../html/login_sid.xml
1833                         parms = urlencode({'getpage':'../html/login_sid.xml'})
1834                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
1835                         debug("[FritzCallFBF_05_27] _login: '" + url + "?" + parms + "'")
1836                         getPage(url,
1837                                 method="POST",
1838                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1839                                                 }, postdata=parms).addCallback(self._md5Login).addErrback(self._errorLogin)
1840
1841         def _md5Login(self, sidXml):
1842                 def buildResponse(challenge, text):
1843                         debug("[FritzCallFBF_05_27] _md5Login7buildResponse: challenge: " + challenge + ' text: ' + text)
1844                         text = (challenge + '-' + text).decode('utf-8','ignore').encode('utf-16-le')
1845                         for i in range(len(text)):
1846                                 if ord(text[i]) > 255:
1847                                         text[i] = '.'
1848                         md5 = hashlib.md5()
1849                         md5.update(text)
1850                         debug("[FritzCallFBF_05_27] md5Login/buildResponse: " + md5.hexdigest())
1851                         return challenge + '-' + md5.hexdigest()
1852
1853                 debug("[FritzCallFBF_05_27] _md5Login")
1854                 found = re.match('.*<SID>([^<]*)</SID>', sidXml, re.S)
1855                 if found:
1856                         self._md5Sid = found.group(1)
1857                         debug("[FritzCallFBF_05_27] _md5Login: SID "+ self._md5Sid)
1858                 else:
1859                         debug("[FritzCallFBF_05_27] _md5Login: no sid! That must be an old firmware.")
1860                         self._errorLogin('No sid?!?')
1861                         return
1862
1863                 debug("[FritzCallFBF_05_27] _md5Login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
1864                 self._md5LoginTimestamp = time.time()
1865                 if sidXml.find('<iswriteaccess>0</iswriteaccess>') != -1:
1866                         debug("[FritzCallFBF_05_27] _md5Login: logging in")
1867                         found = re.match('.*<Challenge>([^<]*)</Challenge>', sidXml, re.S)
1868                         if found:
1869                                 challenge = found.group(1)
1870                                 debug("[FritzCallFBF_05_27] _md5Login: challenge " + challenge)
1871                         else:
1872                                 challenge = None
1873                                 debug("[FritzCallFBF_05_27] _md5Login: login necessary and no challenge! That is terribly wrong.")
1874                         parms = urlencode({
1875                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
1876                                                         'login:command/response': buildResponse(challenge, config.plugins.FritzCall.password.value),
1877                                                         })
1878                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
1879                         debug("[FritzCallFBF_05_27] _md5Login: '" + url + "?" + parms + "'")
1880                         getPage(url,
1881                                 method="POST",
1882                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1883                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1884                                                 }, postdata=parms).addCallback(self._gotPageLogin).addErrback(self._errorLogin)
1885                 else:
1886                         for callback in self._loginCallbacks:
1887                                 debug("[FritzCallFBF_05_27] _md5Login: calling " + callback.__name__)
1888                                 callback(None)
1889                         self._loginCallbacks = []
1890
1891         def _gotPageLogin(self, html):
1892                 if self._callScreen:
1893                         self._callScreen.updateStatus(_("login verification"))
1894                 debug("[FritzCallFBF_05_27] _gotPageLogin: verify login")
1895                 start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1896                 if start != -1:
1897                         start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1898                         text = _("FRITZ!Box - Error logging in\n\n") + html[start : html.find('</p>', start)]
1899                         self._notify(text)
1900                 else:
1901                         if self._callScreen:
1902                                 self._callScreen.updateStatus(_("login ok"))
1903
1904                 found = re.match('.*<input type="hidden" name="sid" value="([^\"]*)"', html, re.S)
1905                 if found:
1906                         self._md5Sid = found.group(1)
1907                         debug("[FritzCallFBF_05_27] _gotPageLogin: found sid: " + self._md5Sid)
1908
1909                 for callback in self._loginCallbacks:
1910                         debug("[FritzCallFBF_05_27] _gotPageLogin: calling " + callback.__name__)
1911                         callback(None)
1912                 self._loginCallbacks = []
1913
1914         def _errorLogin(self, error):
1915                 global fritzbox
1916                 debug("[FritzCallFBF_05_27] _errorLogin: %s" % (error))
1917                 if type(error) != str:
1918                         error =  error.getErrorMessage()
1919                 text = _("FRITZ!Box - Error logging in: %s\nDisabling plugin.") % error
1920                 # config.plugins.FritzCall.enable.value = False
1921                 fritzbox = None
1922                 self._notify(text)
1923
1924         def _logout(self):
1925                 if self._md5LoginTimestamp:
1926                         self._md5LoginTimestamp = None
1927                         parms = urlencode({
1928                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
1929                                                         'login:command/logout':'bye bye Fritz'
1930                                                         })
1931                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
1932                         debug("[FritzCallFBF_05_27] logout: '" + url + "' parms: '" + parms + "'")
1933                         getPage(url,
1934                                 method="POST",
1935                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1936                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1937                                                 }, postdata=parms).addErrback(self._errorLogout)
1938
1939         def _errorLogout(self, error):
1940                 debug("[FritzCallFBF_05_27] _errorLogout: %s" % (error))
1941                 text = _("FRITZ!Box - Error logging out: %s") % error.getErrorMessage()
1942                 self._notify(text)
1943
1944         def loadFritzBoxPhonebook(self, phonebook):
1945                 self.phonebook = phonebook
1946                 self._login(self._selectFritzBoxPhonebook)
1947
1948         def _selectFritzBoxPhonebook(self, html):
1949                 # first check for login error
1950                 if html:
1951                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1952                         if start != -1:
1953                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1954                                 self._errorLoad('Login: ' + html[start, html.find('</p>', start)])
1955                                 return
1956                 # look for phonebook called dreambox or Dreambox
1957                 parms = urlencode({
1958                                                 'sid':self._md5Sid,
1959                                                 })
1960                 url = "http://%s/fon_num/fonbook_select.lua" % (config.plugins.FritzCall.hostname.value)
1961                 debug("[FritzCallFBF_05_27] _selectPhonebook: '" + url + "' parms: '" + parms + "'")
1962                 getPage(url,
1963                         method="POST",
1964                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1965                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1966                                         }, postdata=parms).addCallback(self._loadFritzBoxPhonebook).addErrback(self._errorLoad)
1967
1968         def _loadFritzBoxPhonebook(self, html):
1969                 # Firmware 05.27 onwards
1970                 # look for phonebook called [dD]reambox and get bookid
1971                 found = re.match('.*<label for="uiBookid:([\d]+)">[dD]reambox', html, re.S)
1972                 if found:
1973                         bookid = found.group(1)
1974                         debug("[FritzCallFBF_05_27] _loadFritzBoxPhonebook: found dreambox phonebook %s" % (bookid))
1975                 else:
1976                         bookid = 1
1977                 # http://192.168.178.1/fon_num/fonbook_list.lua?sid=2faec13b0000f3a2
1978                 parms = urlencode({
1979                                                 'bookid':bookid,
1980                                                 'sid':self._md5Sid,
1981                                                 })
1982                 url = "http://%s/fon_num/fonbook_list.lua" % (config.plugins.FritzCall.hostname.value)
1983                 debug("[FritzCallFBF_05_27] _loadFritzBoxPhonebookNew: '" + url + "' parms: '" + parms + "'")
1984                 getPage(url,
1985                         method="POST",
1986                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1987                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1988                                         }, postdata=parms).addCallback(self._parseFritzBoxPhonebook).addErrback(self._errorLoad)
1989
1990         def _parseFritzBoxPhonebook(self, html):
1991                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew")
1992                 #=======================================================================
1993                 # found = re.match('.*<input type="hidden" name="telcfg:settings/Phonebook/Books/Name\d+" value="[Dd]reambox" id="uiPostPhonebookName\d+" disabled>\s*<input type="hidden" name="telcfg:settings/Phonebook/Books/Id\d+" value="(\d+)" id="uiPostPhonebookId\d+" disabled>', html, re.S)
1994                 # if found:
1995                 #       phoneBookID = found.group(1)
1996                 #       debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: found dreambox phonebook with id: " + phoneBookID)
1997                 #       if self._phoneBookID != phoneBookID:
1998                 #               self._phoneBookID = phoneBookID
1999                 #               debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: reload phonebook")
2000                 #               self._loadFritzBoxPhonebook(None) # reload with dreambox phonebook
2001                 #               return
2002                 #=======================================================================
2003
2004                 # first, let us get the charset
2005                 found = re.match('.*<meta http-equiv=content-type content="text/html; charset=([^"]*)">', html, re.S)
2006                 if found:
2007                         charset = found.group(1)
2008                         debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: found charset: " + charset)
2009                         html = html2unicode(html.replace(chr(0xf6),'').decode(charset)).encode('utf-8')
2010                 else: # this is kind of emergency conversion...
2011                         try:
2012                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: try charset utf-8")
2013                                 charset = 'utf-8'
2014                                 html = html2unicode(html.decode('utf-8')).encode('utf-8') # this looks silly, but has to be
2015                         except UnicodeDecodeError:
2016                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: try charset iso-8859-1")
2017                                 charset = 'iso-8859-1'
2018                                 html = html2unicode(html.decode('iso-8859-1')).encode('utf-8') # this looks silly, but has to be
2019
2020                 # cleanout hrefs
2021                 html = re.sub("<a href[^>]*>", "", html)
2022                 html = re.sub("</a>", "", html)
2023                 #=======================================================================
2024                 # linkP = open("/tmp/FritzCall_Phonebook.htm", "w")
2025                 # linkP.write(html)
2026                 # linkP.close()
2027                 #=======================================================================
2028
2029                 if html.find('class="zebra_reverse"') != -1:
2030                         debug("[FritzCallFBF_05_27] Found new 7390 firmware")
2031                         # <td class="tname">Mama</td><td class="tnum">03602191620<br>015228924783<br>03602181567</td><td class="ttype">geschäftl.<br>mobil<br>privat</td><td class="tcode"><br>**701<br></td><td class="tvanity"><br>1<br></td>
2032                         entrymask = re.compile('<td class="tname">([^<]*)</td><td class="tnum">([^<]+(?:<br>[^<]+)*)</td><td class="ttype">([^<]+(?:<br>[^<]+)*)</td><td class="tcode">([^<]*(?:<br>[^<]*)*)</td><td class="tvanity">([^<]*(?:<br>[^<]*)*)</td>', re.S)
2033                         entries = entrymask.finditer(html)
2034                         for found in entries:
2035                                 # debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: processing entry for '''%s'''" % (found.group(1)))
2036                                 name = found.group(1)
2037                                 thisnumbers = found.group(2).split("<br>")
2038                                 thistypes = found.group(3).split("<br>")
2039                                 thiscodes = found.group(4).split("<br>")
2040                                 thisvanitys = found.group(5).split("<br>")
2041                                 for i in range(len(thisnumbers)):
2042                                         thisnumber = cleanNumber(thisnumbers[i])
2043                                         if self.phonebook.phonebook.has_key(thisnumber):
2044                                                 debug("[FritzCallFBF_05_27] Ignoring '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (name, thisnumber))
2045                                                 continue
2046
2047                                         if not thisnumbers[i]:
2048                                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: Ignoring entry with empty number for '''%s'''" % (name))
2049                                                 continue
2050                                         else:
2051                                                 thisname = name
2052                                                 if config.plugins.FritzCall.showType.value and thistypes[i]:
2053                                                         thisname = thisname + " (" + thistypes[i] + ")"
2054                                                 if config.plugins.FritzCall.showShortcut.value and thiscodes[i]:
2055                                                         thisname = thisname + ", " + _("Shortcut") + ": " + thiscodes[i]
2056                                                 if config.plugins.FritzCall.showVanity.value and thisvanitys[i]:
2057                                                         thisname = thisname + ", " + _("Vanity") + ": " + thisvanitys[i]
2058         
2059                                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (thisname.strip(), thisnumber))
2060                                                 # Beware: strings in phonebook.phonebook have to be in utf-8!
2061                                                 self.phonebook.phonebook[thisnumber] = thisname
2062                 else:
2063                         self._notify(_("Could not parse FRITZ!Box Phonebook entry"))
2064
2065         def _errorLoad(self, error):
2066                 debug("[FritzCallFBF_05_27] _errorLoad: %s" % (error))
2067                 text = _("FRITZ!Box - Could not load phonebook: %s") % error.getErrorMessage()
2068                 self._notify(text)
2069
2070         def getCalls(self, callScreen, callback, callType):
2071                 #
2072                 # FW 05.27 onwards
2073                 #
2074                 self._callScreen = callScreen
2075                 self._callType = callType
2076                 debug("[FritzCallFBF_05_27] _getCalls1New")
2077                 if self._callScreen:
2078                         self._callScreen.updateStatus(_("finishing"))
2079                 # http://192.168.178.1/fon_num/foncalls_list.lua?sid=da78ab0797197dc7
2080                 parms = urlencode({'sid':self._md5Sid})
2081                 url = "http://%s/fon_num/foncalls_list.lua?%s" % (config.plugins.FritzCall.hostname.value, parms)
2082                 getPage(url).addCallback(lambda x:self._gotPageCalls(callback, x)).addErrback(self._errorCalls)
2083
2084         def _gotPageCalls(self, callback, html=""):
2085
2086                 debug("[FritzCallFBF_05_27] _gotPageCalls")
2087                 if self._callScreen:
2088                         self._callScreen.updateStatus(_("preparing"))
2089
2090                 callListL = []
2091                 if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
2092                         filtermsns = map(lambda x: x.strip(), config.plugins.FritzCall.filtermsn.value.split(","))
2093                         debug("[FritzCallFBF_05_27] _gotPageCalls: filtermsns %s" % (repr(filtermsns)))
2094
2095                 #=======================================================================
2096                 # linkP = open("/tmp/FritzCall_Calllist.htm", "w")
2097                 # linkP.write(html)
2098                 # linkP.close()
2099                 #=======================================================================
2100
2101                 # 1: direct; 2: date; 3: Rufnummer; 4: Name; 5: Nebenstelle; 6: Eigene Rufnumme lang; 7: Eigene Rufnummer; 8: Dauer
2102                 entrymask = re.compile('<td class="([^"]*)" title="[^"]*"></td>\s*<td>([^<]*)</td>\s*<td(?: title="[^\d]*)?([\d]*)(?:[">]+)?(?:<a href=[^>]*>)?([^<]*)(?:</a>)?</td>\s*<td>([^<]*)</td>\s*<td title="([^"]*)">([\d]*)</td>\s*<td>([^<]*)</td>', re.S)
2103                 entries = entrymask.finditer(html)
2104                 for found in entries:
2105                         if found.group(1) == "call_in":
2106                                 direct = FBF_IN_CALLS
2107                         elif found.group(1) == "call_out":
2108                                 direct = FBF_OUT_CALLS
2109                         elif found.group(1) == "call_in_fail":
2110                                 direct = FBF_MISSED_CALLS
2111                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: direct: " + direct)
2112                         if direct != self._callType and "." != self._callType:
2113                                 continue
2114
2115                         date = found.group(2)
2116                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: date: " + date)
2117                         length = found.group(8)
2118                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: len: " + length)
2119                         remote = found.group(4)
2120                         if config.plugins.FritzCall.phonebook.value:
2121                                 if remote and not remote.isdigit():
2122                                         remote = resolveNumber(found.group(3), remote + " (FBF)", self.phonebook)
2123                                 else:
2124                                         remote = resolveNumber(found.group(3), "", self.phonebook)
2125                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: remote. " + remote)
2126                         here = found.group(7)
2127                         #===================================================================
2128                         # start = here.find('Internet: ')
2129                         # if start != -1:
2130                         #       start += len('Internet: ')
2131                         #       here = here[start:]
2132                         # else:
2133                         #       here = line[5]
2134                         #===================================================================
2135                         if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
2136                                 # debug("[FritzCallFBF_05_27] _gotPageCalls: check %s" % (here))
2137                                 if here not in filtermsns:
2138                                         # debug("[FritzCallFBF_05_27] _gotPageCalls: skip %s" % (here))
2139                                         continue
2140                         here = resolveNumber(here, found.group(6), self.phonebook)
2141                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: here: " + here)
2142
2143                         number = stripCbCPrefix(found.group(3), config.plugins.FritzCall.country.value)
2144                         if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
2145                                 number = config.plugins.FritzCall.prefix.value + number
2146                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: number: " + number)
2147                         debug("[FritzCallFBF_05_27] _gotPageCallsNew: append: %s" % repr((number, date, direct, remote, length, here)) )
2148                         callListL.append((number, date, direct, remote, length, here))
2149
2150                 if callback:
2151                         # debug("[FritzCallFBF_05_27] _gotPageCalls call callback with\n" + text
2152                         callback(callListL)
2153                 self._callScreen = None
2154
2155         def _errorCalls(self, error):
2156                 debug("[FritzCallFBF_05_27] _errorCalls: %s" % (error))
2157                 text = _("FRITZ!Box - Could not load calls: %s") % error.getErrorMessage()
2158                 self._notify(text)
2159
2160         def dial(self, number):
2161                 ''' initiate a call to number '''
2162                 self._login(lambda x: self._dial(number, x))
2163                 
2164         def _dial(self, number, html):
2165                 if html:
2166                         #===================================================================
2167                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
2168                         # if found:
2169                         #       self._errorDial('Login: ' + found.group(1))
2170                         #       return
2171                         #===================================================================
2172                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
2173                         if start != -1:
2174                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
2175                                 self._errorDial('Login: ' + html[start, html.find('</p>', start)])
2176                                 return
2177                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
2178                 parms = urlencode({
2179                         'getpage':'../html/de/menus/menu2.html',
2180                         'var:pagename':'fonbuch',
2181                         'var:menu':'home',
2182                         'telcfg:settings/UseClickToDial':'1',
2183                         'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
2184                         'telcfg:command/Dial':number,
2185                         'sid':self._md5Sid
2186                         })
2187                 debug("[FritzCallFBF_05_27] dial url: '" + url + "' parms: '" + parms + "'")
2188                 getPage(url,
2189                         method="POST",
2190                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
2191                         headers={
2192                                         'Content-Type': "application/x-www-form-urlencoded",
2193                                         'Content-Length': str(len(parms))},
2194                         postdata=parms).addCallback(self._okDial).addErrback(self._errorDial)
2195
2196         def _okDial(self, html): #@UnusedVariable # pylint: disable=W0613
2197                 debug("[FritzCallFBF_05_27] okDial")
2198
2199         def _errorDial(self, error):
2200                 debug("[FritzCallFBF_05_27] errorDial: $s" % error)
2201                 text = _("FRITZ!Box - Dialling failed: %s") % error.getErrorMessage()
2202                 self._notify(text)
2203
2204         def changeWLAN(self, statusWLAN):
2205                 ''' get status info from FBF '''
2206                 debug("[FritzCallFBF_05_27] changeWLAN start")
2207                 Notifications.AddNotification(MessageBox, _("not yet implemented"), type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
2208                 return
2209
2210                 if not statusWLAN or (statusWLAN != '1' and statusWLAN != '0'):
2211                         return
2212                 self._login(lambda x: self._changeWLAN(statusWLAN, x))
2213                 
2214         def _changeWLAN(self, statusWLAN, html):
2215                 if html:
2216                         #===================================================================
2217                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
2218                         # if found:
2219                         #       self._errorChangeWLAN('Login: ' + found.group(1))
2220                         #       return
2221                         #===================================================================
2222                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
2223                         if start != -1:
2224                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
2225                                 self._errorChangeWLAN('Login: ' + html[start, html.find('</p>', start)])
2226                                 return
2227
2228                 if statusWLAN == '0':
2229                         statusWLAN = 'off'
2230                 else:
2231                         statusWLAN = 'off'
2232
2233                 url = "http://%s//wlan/wlan_settings.lua" % config.plugins.FritzCall.hostname.value
2234                 parms = urlencode({
2235                         'active':str(statusWLAN),
2236                         'sid':self._md5Sid
2237                         })
2238                 debug("[FritzCallFBF] changeWLAN url: '" + url + "' parms: '" + parms + "'")
2239                 getPage(url,
2240                         method="POST",
2241                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
2242                         headers={
2243                                         'Content-Type': "application/x-www-form-urlencoded",
2244                                         'Content-Length': str(len(parms))},
2245                         postdata=parms).addCallback(self._okChangeWLAN).addErrback(self._errorChangeWLAN)
2246
2247         def _okChangeWLAN(self, html): #@UnusedVariable # pylint: disable=W0613
2248                 debug("[FritzCallFBF] _okChangeWLAN")
2249
2250         def _errorChangeWLAN(self, error):
2251                 debug("[FritzCallFBF] _errorChangeWLAN: $s" % error)
2252                 text = _("FRITZ!Box - Failed changing WLAN: %s") % error.getErrorMessage()
2253                 self._notify(text)
2254
2255         def changeMailbox(self, whichMailbox):
2256                 ''' switch mailbox on/off '''
2257                 debug("[FritzCallFBF_05_27] changeMailbox start: " + str(whichMailbox))
2258                 Notifications.AddNotification(MessageBox, _("not yet implemented"), type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
2259
2260         def _changeMailbox(self, whichMailbox, html):
2261                 return
2262
2263         def _okChangeMailbox(self, html): #@UnusedVariable # pylint: disable=W0613
2264                 debug("[FritzCallFBF_05_27] _okChangeMailbox")
2265
2266         def _errorChangeMailbox(self, error):
2267                 debug("[FritzCallFBF_05_27] _errorChangeMailbox: $s" % error)
2268                 text = _("FRITZ!Box - Failed changing Mailbox: %s") % error.getErrorMessage()
2269                 self._notify(text)
2270
2271         def getInfo(self, callback):
2272                 ''' get status info from FBF '''
2273                 debug("[FritzCallFBF_05_27] getInfo")
2274                 self._login(lambda x:self._getInfo(callback, x))
2275                 
2276         def _getInfo(self, callback, html):
2277                 debug("[FritzCallFBF_05_27] _getInfo: verify login")
2278                 if html:
2279                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
2280                         if start != -1:
2281                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
2282                                 self._errorGetInfo('Login: ' + html[start, html.find('</p>', start)])
2283                                 return
2284
2285                 self._readBlacklist()
2286
2287                 url = "http://%s/home/home.lua" % config.plugins.FritzCall.hostname.value
2288                 parms = urlencode({
2289                         'sid':self._md5Sid
2290                         })
2291                 debug("[FritzCallFBF_05_27] _getInfo url: '" + url + "' parms: '" + parms + "'")
2292                 getPage(url,
2293                         method="POST",
2294                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
2295                         headers={
2296                                         'Content-Type': "application/x-www-form-urlencoded",
2297                                         'Content-Length': str(len(parms))},
2298                         postdata=parms).addCallback(lambda x:self._okGetInfo(callback,x)).addErrback(self._errorGetInfo)
2299
2300         def _okGetInfo(self, callback, html):
2301
2302                 debug("[FritzCallFBF_05_27] _okGetInfo")
2303
2304                 #=======================================================================
2305                 # linkP = open("/tmp/FritzCallInfo.htm", "w")
2306                 # linkP.write(html)
2307                 # linkP.close()
2308                 #=======================================================================
2309
2310                 if self.info:
2311                         (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
2312                 else:
2313                         (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = (None, None, None, None, None, None, None, None, None)
2314
2315                 found = re.match('.*<table id="tProdukt" class="tborder"> <tr> <td style="[^"]*" >([^<]*)</td> <td style="[^"]*" class="td_right">([^<]*)<a target="[^"]*" onclick="[^"]*" href="[^"]*">([^<]*)</a></td> ', html, re.S)
2316                 if found:
2317                         boxInfo = found.group(1) + ', ' + found.group(2) + found.group(3)
2318                         boxInfo = boxInfo.replace('&nbsp;',' ')
2319                         debug("[FritzCallFBF_05_27] _okGetInfo Boxinfo: " + boxInfo)
2320
2321                 found = re.match('.*<div id=\'ipv4_info\'><span class="[^"]*">verbunden seit ([^<]*)</span>', html, re.S)
2322                 if found:
2323                         upTime = found.group(1)
2324                         debug("[FritzCallFBF_05_27] _okGetInfo upTime: " + upTime)
2325
2326                 found = re.match('.*IP-Adresse: ([^<]*)</span>', html, re.S)
2327                 if found:
2328                         ipAddress = found.group(1)
2329                         debug("[FritzCallFBF_05_27] _okGetInfo ipAddress: " + ipAddress)
2330
2331                 # wlanstate = [ active, encrypted, no of devices ]
2332                 found = re.match('.*<tr id="uiTrWlan"><td class="(led_gray|led_green|led_red)"></td><td><a href="[^"]*">WLAN</a></td><td>(aus|an)(|, gesichert)</td>', html, re.S)
2333                 if found:
2334                         if found.group(1) == "led_green":
2335                                 if found.group(2):
2336                                         wlanState = [ '1', '1', '' ]
2337                                 else:
2338                                         wlanState = [ '1', '0', '' ]
2339                         else:
2340                                 wlanState = [ '0', '0', '0' ]
2341                         debug("[FritzCallFBF_05_27] _okGetInfo wlanState: " + repr(wlanState))
2342
2343                 found = re.match('.*<tr id="uiTrDsl"><td class="(led_gray|led_green|led_red)">', html, re.S)
2344                 if found:
2345                         if found.group(1) == "led_green":
2346                                 dslState = ['5', None, None]
2347                                 found = re.match('.*<a href="[^"]*">DSL</a></td><td >bereit, ([^<]*)<img src=\'[^\']*\' height=\'[^\']*\'>&nbsp;([^<]*)<img src=\'[^\']*\' height=\'[^\']*\'></td></tr>', html, re.S)
2348                                 if found:
2349                                         dslState[1] = found.group(1) + "/" + found.group(2)
2350                         else:
2351                                 dslState = ['0', None, None]
2352                 debug("[FritzCallFBF_05_27] _okGetInfo dslState: " + repr(dslState))
2353
2354                 found = re.match('.*<tr id="trTam" style=""><td><a href="[^"]*">Anrufbeantworter</a></td><td title=\'[^\']*\'>([\d]+) aktiv([^<]*)</td></tr>', html, re.S)
2355                 if found:
2356                         # found.group(2) could be ', neue Nachrichten vorhanden'; ignore for now
2357                         tamActive = [ found.group(1), False, False, False, False, False]
2358                 debug("[FritzCallFBF_05_27] _okGetInfo tamActive: " + repr(tamActive))
2359
2360                 found = re.match('.*<tr id="uiTrDect"><td class="led_green"></td><td><a href="[^"]*">DECT</a></td><td>an, (ein|\d*) Schnurlostelefon', html, re.S)
2361                 if found:
2362                         dectActive = found.group(1)
2363                 debug("[FritzCallFBF_05_27] _okGetInfo dectActive: " + repr(dectActive))
2364
2365                 found = re.match('.*<td>Integriertes Fax aktiv</td>', html, re.S)
2366                 if found:
2367                         faxActive = True
2368                 debug("[FritzCallFBF_05_27] _okGetInfo faxActive: " + repr(faxActive))
2369
2370                 found = re.match('.* <tr style=""><td><a href="[^"]*">Rufumleitung</a></td><td>deaktiviert</td></tr>', html, re.S)
2371                 if found:
2372                         rufumlActive = False
2373                 else:
2374                         rufumlActive = True
2375                 debug("[FritzCallFBF_05_27] _okGetInfo rufumlActive: " + repr(rufumlActive))
2376
2377                 info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
2378                 debug("[FritzCallFBF_05_27] _okGetInfo info: " + str(info))
2379                 self.info = info
2380                 if callback:
2381                         callback(info)
2382
2383         def _okSetDect(self, callback, html):
2384                 return
2385         
2386         def _okSetConInfo(self, callback, html):
2387                 return
2388
2389         def _okSetWlanState(self, callback, html):
2390                 return
2391
2392         def _okSetDslState(self, callback, html):
2393                 return
2394
2395         def _errorGetInfo(self, error):
2396                 debug("[FritzCallFBF_05_27] _errorGetInfo: %s" % (error))
2397                 text = _("FRITZ!Box - Error getting status: %s") % error.getErrorMessage()
2398                 self._notify(text)
2399                 return
2400
2401         def reset(self):
2402                 self._login(self._reset)
2403
2404         def _reset(self, html):
2405                 # POSTDATA=getpage=../html/reboot.html&errorpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:errorpagename=home&var:menu=home&var:pagemaster=&time:settings/time=1242207340%2C-120&var:tabReset=0&logic:command/reboot=../gateway/commands/saveconfig.html
2406                 if html:
2407                         #===================================================================
2408                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
2409                         # if found:
2410                         #       self._errorReset('Login: ' + found.group(1))
2411                         #       return
2412                         #===================================================================
2413                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
2414                         if start != -1:
2415                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
2416                                 self._errorReset('Login: ' + html[start, html.find('</p>', start)])
2417                                 return
2418                 if self._callScreen:
2419                         self._callScreen.close()
2420                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
2421                 parms = urlencode({
2422                         'getpage':'../html/reboot.html',
2423                         'var:lang':'de',
2424                         'var:pagename':'reset',
2425                         'var:menu':'system',
2426                         'logic:command/reboot':'../gateway/commands/saveconfig.html',
2427                         'sid':self._md5Sid
2428                         })
2429                 debug("[FritzCallFBF_05_27] _reset url: '" + url + "' parms: '" + parms + "'")
2430                 getPage(url,
2431                         method="POST",
2432                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
2433                         headers={
2434                                         'Content-Type': "application/x-www-form-urlencoded",
2435                                         'Content-Length': str(len(parms))},
2436                         postdata=parms)
2437
2438         def _okReset(self, html): #@UnusedVariable # pylint: disable=W0613
2439                 debug("[FritzCallFBF_05_27] _okReset")
2440
2441         def _errorReset(self, error):
2442                 debug("[FritzCallFBF_05_27] _errorReset: %s" % (error))
2443                 text = _("FRITZ!Box - Error resetting: %s") % error.getErrorMessage()
2444                 self._notify(text)
2445
2446         def _readBlacklist(self):
2447                 # http://fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=sperre
2448                 url = "http://%s/fon_num/sperre.lua" % config.plugins.FritzCall.hostname.value
2449                 parms = urlencode({
2450                         'sid':self._md5Sid
2451                         })
2452                 debug("[FritzCallFBF_05_27] _readBlacklist url: '" + url + "' parms: '" + parms + "'")
2453                 getPage(url,
2454                         method="POST",
2455                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
2456                         headers={
2457                                         'Content-Type': "application/x-www-form-urlencoded",
2458                                         'Content-Length': str(len(parms))},
2459                         postdata=parms).addCallback(self._okBlacklist).addErrback(self._errorBlacklist)
2460
2461         def _okBlacklist(self, html):
2462                 debug("[FritzCallFBF_05_27] _okBlacklist")
2463                 #=======================================================================
2464                 # linkP = open("/tmp/FritzCallBlacklist.htm", "w")
2465                 # linkP.write(html)
2466                 # linkP.close()
2467                 #=======================================================================
2468                 entries = re.compile('<span title="(?:Ankommende|Ausgehende) Rufe">(Ankommende|Ausgehende) Rufe</span></nobr></td><td><nobr><span title="[\d]+">([\d]+)</span>', re.S).finditer(html)
2469                 self.blacklist = ([], [])
2470                 for entry in entries:
2471                         if entry.group(1) == "Ankommende":
2472                                 self.blacklist[0].append(entry.group(2))
2473                         else:
2474                                 self.blacklist[1].append(entry.group(2))
2475                 debug("[FritzCallFBF_05_27] _okBlacklist: %s" % repr(self.blacklist))
2476
2477         def _errorBlacklist(self, error):
2478                 debug("[FritzCallFBF_05_27] _errorBlacklist: %s" % (error))
2479                 text = _("FRITZ!Box - Error getting blacklist: %s") % error.getErrorMessage()
2480                 self._notify(text)