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