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