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