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