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