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