FIX: small correction
[enigma2-plugins.git] / fritzcall / src / FritzCallFBF.py
1 # -*- coding: utf-8 -*-
2 '''
3 Created on 30.09.2012
4 $Author: michael $
5 $Revision: 1 $
6 $Date: 2012-09-30 13:37:48 +0200 (Sun, 30 Sep 2012) $
7 $Id: plugin.py 685 2012-09-30 11:37:48Z 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._phoneBookID = '0'
80                 self.info = None # (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive)
81                 self.getInfo(None)
82                 self.blacklist = ([], [])
83                 self.readBlacklist()
84                 self.phonebook = None
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" 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''' from FRITZ!Box Phonebook!" % (thisname.strip(), thisnumber))
345                                                         self.phonebook.phonebook[thisnumber] = thisname
346                                                 else:
347                                                         debug("[FritzCallFBF] Ignoring '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (thisname.strip(), thisnumber))
348
349                 # elif re.search('document.write\(TrFon\(', html):
350                 elif html.find('document.write(TrFon(') != -1:
351                         #===============================================================================
352                         #                               Old Style: 7050 (FW 14.04.33)
353                         #       We expect one line with TrFon(No,Name,Number,Shortcut,Vanity)
354                         #   Encoding should be plain Ascii...
355                         #===============================================================================                                
356                         entrymask = re.compile('TrFon\("[^"]*", "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)"\)', re.S)
357                         entries = entrymask.finditer(html)
358                         for found in entries:
359                                 name = found.group(1).strip().replace(',','')
360                                 # debug("[FritzCallFBF] pos: %s name: %s" %(found.group(0),name))
361                                 thisnumber = found.group(2).strip()
362                                 if config.plugins.FritzCall.showShortcut.value and found.group(3):
363                                         name = name + ", " + _("Shortcut") + ": " + found.group(3)
364                                 if config.plugins.FritzCall.showVanity.value and found.group(4):
365                                         name = name + ", " + _("Vanity") + ": " + found.group(4)
366                                 if thisnumber:
367                                         # name = name.encode('utf-8')
368                                         # Beware: strings in phonebook.phonebook have to be in utf-8!
369                                         if not self.phonebook.phonebook.has_key(thisnumber):
370                                                 debug("[FritzCallFBF] Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (name, thisnumber))
371                                                 self.phonebook.phonebook[thisnumber] = name
372                                         else:
373                                                 debug("[FritzCallFBF] Ignoring '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (name, thisnumber))
374                                 else:
375                                         debug("[FritzCallFBF] ignoring empty number for %s" % name)
376                                 continue
377                 elif self._md5Sid == '0000000000000000': # retry, it could be a race condition
378                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: retry loading phonebook")
379                         self.loadFritzBoxPhonebook(self.phonebook)
380                 else:
381                         debug("[FritzCallFBF] _parseFritzBoxPhonebook: could not read FBF phonebook; wrong version?")
382                         self._notify(_("Could not read FRITZ!Box phonebook; wrong version?"))
383
384         def _errorLoad(self, error):
385                 debug("[FritzCallFBF] _errorLoad: %s" % (error))
386                 text = _("FRITZ!Box - Could not load phonebook: %s") % error.getErrorMessage()
387                 self._notify(text)
388
389         def getCalls(self, callScreen, callback, callType):
390                 #
391                 # call sequence must be:
392                 # - login
393                 # - getPage -> _gotPageLogin
394                 # - loginCallback (_getCalls)
395                 # - getPage -> _getCalls1
396                 debug("[FritzCallFBF] getCalls")
397                 self._callScreen = callScreen
398                 self._callType = callType
399                 if (time.time() - self._callTimestamp) > 180: 
400                         debug("[FritzCallFBF] getCalls: outdated data, login and get new ones: " + time.ctime(self._callTimestamp) + " time: " + time.ctime())
401                         self._callTimestamp = time.time()
402                         self._login(lambda x:self._getCalls(callback, x))
403                 elif not self._callList:
404                         debug("[FritzCallFBF] getCalls: time is ok, but no callList")
405                         self._getCalls1(callback)
406                 else:
407                         debug("[FritzCallFBF] getCalls: time is ok, callList is ok")
408                         self._gotPageCalls(callback)
409
410         def _getCalls(self, callback, html):
411                 if html:
412                         #===================================================================
413                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
414                         # if found:
415                         #       self._errorCalls('Login: ' + found.group(1))
416                         #       return
417                         #===================================================================
418                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
419                         if start != -1:
420                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
421                                 self._notify('Login: ' + html[start, html.find('</p>', start)])
422                                 return
423                 #
424                 # we need this to fill Anrufliste.csv
425                 # http://repeater1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=foncalls
426                 #
427                 debug("[FritzCallFBF] _getCalls")
428                 if html:
429                         #===================================================================
430                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
431                         # if found:
432                         #       text = _("FRITZ!Box - Error logging in: %s") + found.group(1)
433                         #       self._notify(text)
434                         #       return
435                         #===================================================================
436                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
437                         if start != -1:
438                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
439                                 self._notify(_("FRITZ!Box - Error logging in: %s") + html[start, html.find('</p>', start)])
440                                 return
441
442                 if self._callScreen:
443                         self._callScreen.updateStatus(_("preparing"))
444                 parms = urlencode({'getpage':'../html/de/menus/menu2.html', 'var:lang':'de', 'var:pagename':'foncalls', 'var:menu':'fon', 'sid':self._md5Sid})
445                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
446                 getPage(url).addCallback(lambda x:self._getCalls1(callback)).addErrback(self._errorCalls) #@UnusedVariable # pylint: disable=W0613
447
448         def _getCalls1(self, callback):
449                 #
450                 # finally we should have successfully lgged in and filled the csv
451                 #
452                 debug("[FritzCallFBF] _getCalls1")
453                 if self._callScreen:
454                         self._callScreen.updateStatus(_("finishing"))
455                 parms = urlencode({'getpage':'../html/de/FRITZ!Box_Anrufliste.csv', 'sid':self._md5Sid})
456                 url = "http://%s/cgi-bin/webcm?%s" % (config.plugins.FritzCall.hostname.value, parms)
457                 getPage(url).addCallback(lambda x:self._gotPageCalls(callback, x)).addErrback(self._errorCalls)
458
459         def _gotPageCalls(self, callback, csv=""):
460
461                 if csv:
462                         debug("[FritzCallFBF] _gotPageCalls: got csv, setting callList")
463                         if self._callScreen:
464                                 self._callScreen.updateStatus(_("done"))
465                         if csv.find('Melden Sie sich mit dem Kennwort der FRITZ!Box an') != -1:
466                                 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.")
467                                 # self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
468                                 self._notify(text)
469                                 return
470
471                         csv = csv.decode('iso-8859-1', 'replace').encode('utf-8', 'replace')
472                         lines = csv.splitlines()
473                         self._callList = lines
474                 elif self._callList:
475                         debug("[FritzCallFBF] _gotPageCalls: got no csv, but have callList")
476                         if self._callScreen:
477                                 self._callScreen.updateStatus(_("done, using last list"))
478                         lines = self._callList
479                 else:
480                         debug("[FritzCallFBF] _gotPageCalls: Could not get call list; wrong version?")
481                         self._notify(_("Could not get call list; wrong version?"))
482                         return
483                         
484                 callListL = []
485                 if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
486                         filtermsns = map(lambda x: x.strip(), config.plugins.FritzCall.filtermsn.value.split(","))
487                         debug("[FritzCallFBF] _gotPageCalls: filtermsns %s" % (repr(filtermsns)))
488
489                 # Typ;Datum;Name;Rufnummer;Nebenstelle;Eigene Rufnummer;Dauer
490                 # 0  ;1    ;2   ;3                ;4              ;5                       ;6
491                 lines = map(lambda line: line.split(';'), lines)
492                 lines = filter(lambda line: (len(line)==7 and (line[0]=="Typ" or self._callType == '.' or line[0] == self._callType)), lines)
493
494                 for line in lines:
495                         # debug("[FritzCallFBF] _gotPageCalls: line %s" % (line))
496                         direct = line[0]
497                         date = line[1]
498                         length = line[6]
499                         if config.plugins.FritzCall.phonebook.value and line[2]:
500                                 remote = resolveNumber(line[3], line[2] + " (FBF)", self.phonebook)
501                         else:
502                                 remote = resolveNumber(line[3], line[2], self.phonebook)
503                         here = line[5]
504                         start = here.find('Internet: ')
505                         if start != -1:
506                                 start += len('Internet: ')
507                                 here = here[start:]
508                         else:
509                                 here = line[5]
510                         if direct != "Typ" and config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
511                                 # debug("[FritzCallFBF] _gotPageCalls: check %s" % (here))
512                                 if here not in filtermsns:
513                                         # debug("[FritzCallFBF] _gotPageCalls: skip %s" % (here))
514                                         continue
515                         here = resolveNumber(here, line[4], self.phonebook)
516
517                         number = stripCbCPrefix(line[3], config.plugins.FritzCall.country.value)
518                         if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
519                                 number = config.plugins.FritzCall.prefix.value + number
520                         callListL.append((number, date, direct, remote, length, here))
521
522                 if callback:
523                         # debug("[FritzCallFBF] _gotPageCalls call callback with\n" + text
524                         callback(callListL)
525                 self._callScreen = None
526
527         def _errorCalls(self, error):
528                 debug("[FritzCallFBF] _errorCalls: %s" % (error))
529                 text = _("FRITZ!Box - Could not load calls: %s") % error.getErrorMessage()
530                 self._notify(text)
531
532         def dial(self, number):
533                 ''' initiate a call to number '''
534                 self._login(lambda x: self._dial(number, x))
535                 
536         def _dial(self, number, html):
537                 if html:
538                         #===================================================================
539                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
540                         # if found:
541                         #       self._errorDial('Login: ' + found.group(1))
542                         #       return
543                         #===================================================================
544                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
545                         if start != -1:
546                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
547                                 self._errorDial('Login: ' + html[start, html.find('</p>', start)])
548                                 return
549                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
550                 parms = urlencode({
551                         'getpage':'../html/de/menus/menu2.html',
552                         'var:pagename':'fonbuch',
553                         'var:menu':'home',
554                         'telcfg:settings/UseClickToDial':'1',
555                         'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
556                         'telcfg:command/Dial':number,
557                         'sid':self._md5Sid
558                         })
559                 debug("[FritzCallFBF] dial url: '" + url + "' parms: '" + parms + "'")
560                 getPage(url,
561                         method="POST",
562                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
563                         headers={
564                                         'Content-Type': "application/x-www-form-urlencoded",
565                                         'Content-Length': str(len(parms))},
566                         postdata=parms).addCallback(self._okDial).addErrback(self._errorDial)
567
568         def _okDial(self, html): #@UnusedVariable # pylint: disable=W0613
569                 debug("[FritzCallFBF] okDial")
570
571         def _errorDial(self, error):
572                 debug("[FritzCallFBF] errorDial: $s" % error)
573                 text = _("FRITZ!Box - Dialling failed: %s") % error.getErrorMessage()
574                 self._notify(text)
575
576         def changeWLAN(self, statusWLAN):
577                 ''' get status info from FBF '''
578                 debug("[FritzCallFBF] changeWLAN start")
579                 if not statusWLAN or (statusWLAN != '1' and statusWLAN != '0'):
580                         return
581                 self._login(lambda x: self._changeWLAN(statusWLAN, x))
582                 
583         def _changeWLAN(self, statusWLAN, html):
584                 if html:
585                         #===================================================================
586                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
587                         # if found:
588                         #       self._errorChangeWLAN('Login: ' + found.group(1))
589                         #       return
590                         #===================================================================
591                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
592                         if start != -1:
593                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
594                                 self._errorChangeWLAN('Login: ' + html[start, html.find('</p>', start)])
595                                 return
596                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
597                 parms = urlencode({
598                         'getpage':'../html/de/menus/menu2.html',
599                         'var:lang':'de',
600                         'var:pagename':'wlan',
601                         'var:menu':'wlan',
602                         'wlan:settings/ap_enabled':str(statusWLAN),
603                         'sid':self._md5Sid
604                         })
605                 debug("[FritzCallFBF] changeWLAN url: '" + url + "' parms: '" + parms + "'")
606                 getPage(url,
607                         method="POST",
608                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
609                         headers={
610                                         'Content-Type': "application/x-www-form-urlencoded",
611                                         'Content-Length': str(len(parms))},
612                         postdata=parms).addCallback(self._okChangeWLAN).addErrback(self._errorChangeWLAN)
613
614         def _okChangeWLAN(self, html): #@UnusedVariable # pylint: disable=W0613
615                 debug("[FritzCallFBF] _okChangeWLAN")
616
617         def _errorChangeWLAN(self, error):
618                 debug("[FritzCallFBF] _errorChangeWLAN: $s" % error)
619                 text = _("FRITZ!Box - Failed changing WLAN: %s") % error.getErrorMessage()
620                 self._notify(text)
621
622         def changeMailbox(self, whichMailbox):
623                 ''' switch mailbox on/off '''
624                 debug("[FritzCallFBF] changeMailbox start: " + str(whichMailbox))
625                 self._login(lambda x: self._changeMailbox(whichMailbox, x))
626
627         def _changeMailbox(self, whichMailbox, html):
628                 if html:
629                         #===================================================================
630                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
631                         # if found:
632                         #       self._errorChangeMailbox('Login: ' + found.group(1))
633                         #       return
634                         #===================================================================
635                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
636                         if start != -1:
637                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
638                                 self._errorChangeMailbox('Login: ' + html[start, html.find('</p>', start)])
639                                 return
640                 debug("[FritzCallFBF] _changeMailbox")
641                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
642                 if whichMailbox == -1:
643                         for i in range(5):
644                                 if self.info[FBF_tamActive][i+1]:
645                                         state = '0'
646                                 else:
647                                         state = '1'
648                                 parms = urlencode({
649                                         'tam:settings/TAM'+str(i)+'/Active':state,
650                                         'sid':self._md5Sid
651                                         })
652                                 debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
653                                 getPage(url,
654                                         method="POST",
655                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
656                                         headers={
657                                                         'Content-Type': "application/x-www-form-urlencoded",
658                                                         'Content-Length': str(len(parms))},
659                                         postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
660                 elif whichMailbox > 4:
661                         debug("[FritzCallFBF] changeMailbox invalid mailbox number")
662                 else:
663                         if self.info[FBF_tamActive][whichMailbox+1]:
664                                 state = '0'
665                         else:
666                                 state = '1'
667                         parms = urlencode({
668                                 'tam:settings/TAM'+str(whichMailbox)+'/Active':state,
669                                 'sid':self._md5Sid
670                                 })
671                         debug("[FritzCallFBF] changeMailbox url: '" + url + "' parms: '" + parms + "'")
672                         getPage(url,
673                                 method="POST",
674                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
675                                 headers={
676                                                 'Content-Type': "application/x-www-form-urlencoded",
677                                                 'Content-Length': str(len(parms))},
678                                 postdata=parms).addCallback(self._okChangeMailbox).addErrback(self._errorChangeMailbox)
679
680         def _okChangeMailbox(self, html): #@UnusedVariable # pylint: disable=W0613
681                 debug("[FritzCallFBF] _okChangeMailbox")
682
683         def _errorChangeMailbox(self, error):
684                 debug("[FritzCallFBF] _errorChangeMailbox: $s" % error)
685                 text = _("FRITZ!Box - Failed changing Mailbox: %s") % error.getErrorMessage()
686                 self._notify(text)
687
688         def getInfo(self, callback):
689                 ''' get status info from FBF '''
690                 debug("[FritzCallFBF] getInfo")
691                 self._login(lambda x:self._getInfo(callback, x))
692                 
693         def _getInfo(self, callback, html):
694                 # http://192.168.178.1/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:pagename=home&var:menu=home
695                 debug("[FritzCallFBF] _getInfo: verify login")
696                 if html:
697                         #===================================================================
698                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
699                         # if found:
700                         #       self._errorGetInfo('Login: ' + found.group(1))
701                         #       return
702                         #===================================================================
703                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
704                         if start != -1:
705                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
706                                 self._errorGetInfo('Login: ' + html[start, html.find('</p>', start)])
707                                 return
708
709                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
710                 parms = urlencode({
711                         'getpage':'../html/de/menus/menu2.html',
712                         'var:lang':'de',
713                         'var:pagename':'home',
714                         'var:menu':'home',
715                         'sid':self._md5Sid
716                         })
717                 debug("[FritzCallFBF] _getInfo url: '" + url + "' parms: '" + parms + "'")
718                 getPage(url,
719                         method="POST",
720                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
721                         headers={
722                                         'Content-Type': "application/x-www-form-urlencoded",
723                                         'Content-Length': str(len(parms))},
724                         postdata=parms).addCallback(lambda x:self._okGetInfo(callback,x)).addErrback(self._errorGetInfo)
725
726         def _okGetInfo(self, callback, html):
727                 def readInfo(html):
728                         if self.info:
729                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
730                         else:
731                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = (None, None, None, None, None, None, None, None, None)
732
733                         debug("[FritzCallFBF] _okGetInfo/readinfo")
734                         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)
735                         if found:
736                                 boxInfo = found.group(1)+ ', ' + found.group(2)
737                                 boxInfo = boxInfo.replace('&nbsp;',' ')
738                                 # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + boxInfo)
739                         else:
740                                 found = re.match('.*<p class="ac">([^<]*)</p>', html, re.S)
741                                 if found:
742                                         # debug("[FritzCallFBF] _okGetInfo Boxinfo: " + found.group(1))
743                                         boxInfo = found.group(1)
744
745                         if html.find('home_coninf.txt') != -1:
746                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
747                                 parms = urlencode({
748                                         'getpage':'../html/de/home/home_coninf.txt',
749                                         'sid':self._md5Sid
750                                         })
751                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
752                                 getPage(url,
753                                         method="POST",
754                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
755                                         headers={
756                                                         'Content-Type': "application/x-www-form-urlencoded",
757                                                         'Content-Length': str(len(parms))},
758                                         postdata=parms).addCallback(lambda x:self._okSetConInfo(callback,x)).addErrback(self._errorGetInfo)
759                         else:
760                                 found = re.match('.*if \(isNaN\(jetzt\)\)\s*return "";\s*var str = "([^"]*)";', html, re.S)
761                                 if found:
762                                         # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
763                                         upTime = found.group(1)
764                                 else:
765                                         found = re.match('.*str = g_pppSeit \+"([^<]*)<br>"\+mldIpAdr;', html, re.S)
766                                         if found:
767                                                 # debug("[FritzCallFBF] _okGetInfo Uptime: " + found.group(1))
768                                                 upTime = found.group(1)
769         
770                                 found = re.match(".*IpAdrDisplay\('([.\d]+)'\)", html, re.S)
771                                 if found:
772                                         # debug("[FritzCallFBF] _okGetInfo IpAdrDisplay: " + found.group(1))
773                                         ipAddress = found.group(1)
774
775                         if html.find('g_tamActive') != -1:
776                                 entries = re.compile('if \("(\d)" == "1"\) {\s*g_tamActive \+= 1;\s*}', re.S).finditer(html)
777                                 tamActive = [0, False, False, False, False, False]
778                                 i = 1
779                                 for entry in entries:
780                                         state = entry.group(1)
781                                         if state == '1':
782                                                 tamActive[0] += 1
783                                                 tamActive[i] = True
784                                         i += 1
785                                 # debug("[FritzCallFBF] _okGetInfo tamActive: " + str(tamActive))
786                 
787                         if html.find('home_dect.txt') != -1:
788                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
789                                 parms = urlencode({
790                                         'getpage':'../html/de/home/home_dect.txt',
791                                         'sid':self._md5Sid
792                                         })
793                                 # debug("[FritzCallFBF] get coninfo: url: '" + url + "' parms: '" + parms + "'")
794                                 getPage(url,
795                                         method="POST",
796                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
797                                         headers={
798                                                         'Content-Type': "application/x-www-form-urlencoded",
799                                                         'Content-Length': str(len(parms))},
800                                         postdata=parms).addCallback(lambda x:self._okSetDect(callback,x)).addErrback(self._errorGetInfo)
801                         else:
802                                 if html.find('countDect2') != -1:
803                                         entries = re.compile('if \("1" == "1"\) countDect2\+\+;', re.S).findall(html)
804                                         dectActive = len(entries)
805                                         # debug("[FritzCallFBF] _okGetInfo dectActive: " + str(dectActive))
806
807                         found = re.match('.*var g_intFaxActive = "0";\s*if \("1" != ""\) {\s*g_intFaxActive = "1";\s*}\s*', html, re.S)
808                         if found:
809                                 faxActive = True
810                                 # debug("[FritzCallFBF] _okGetInfo faxActive")
811
812                         if html.find('cntRufumleitung') != -1:
813                                 entries = re.compile('mode = "1";\s*ziel = "[^"]+";\s*if \(mode == "1" \|\| ziel != ""\)\s*{\s*g_RufumleitungAktiv = true;', re.S).findall(html)
814                                 rufumlActive = len(entries)
815                                 entries = re.compile('if \("([^"]*)"=="([^"]*)"\) isAllIncoming\+\+;', re.S).finditer(html)
816                                 isAllIncoming = 0
817                                 for entry in entries:
818                                         # debug("[FritzCallFBF] _okGetInfo rufumlActive add isAllIncoming")
819                                         if entry.group(1) == entry.group(2):
820                                                 isAllIncoming += 1
821                                 if isAllIncoming == 2 and rufumlActive > 0:
822                                         rufumlActive -= 1
823                                 # debug("[FritzCallFBF] _okGetInfo rufumlActive: " + str(rufumlActive))
824
825                         # /cgi-bin/webcm?getpage=../html/de/home/home_dsl.txt
826                         # alternative through: fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:menu=internet&var:pagename=overview
827                         # { "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": "" } 
828                         if html.find('home_dsl.txt') != -1:
829                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
830                                 parms = urlencode({
831                                         'getpage':'../html/de/home/home_dsl.txt',
832                                         'sid':self._md5Sid
833                                         })
834                                 # debug("[FritzCallFBF] get dsl state: url: '" + url + "' parms: '" + parms + "'")
835                                 getPage(url,
836                                         method="POST",
837                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
838                                         headers={
839                                                         'Content-Type': "application/x-www-form-urlencoded",
840                                                         'Content-Length': str(len(parms))},
841                                         postdata=parms).addCallback(lambda x:self._okSetDslState(callback,x)).addErrback(self._errorGetInfo)
842                         else:
843                                 found = re.match('.*function DslStateDisplay \(state\){\s*var state = "(\d+)";', html, re.S)
844                                 if found:
845                                         # debug("[FritzCallFBF] _okGetInfo DslState: " + found.group(1))
846                                         dslState = [ found.group(1), None ] # state, speed
847                                         found = re.match('.*function DslStateDisplay \(state\){\s*var state = "\d+";.*?if \("3130" != "0"\) str = "([^"]*)";', html, re.S)
848                                         if found:
849                                                 # debug("[FritzCallFBF] _okGetInfo DslSpeed: " + found.group(1).strip())
850                                                 dslState[1] = found.group(1).strip()
851                 
852                         # /cgi-bin/webcm?getpage=../html/de/home/home_wlan.txt
853                         # { "ap_enabled": "1", "active_stations": "0", "encryption": "4", "wireless_stickandsurf_enabled": "0", "is_macfilter_active": "0", "wmm_enabled": "1", "wlan_state": [ "end" ] }
854                         if html.find('home_wlan.txt') != -1:
855                                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
856                                 parms = urlencode({
857                                         'getpage':'../html/de/home/home_wlan.txt',
858                                         'sid':self._md5Sid
859                                         })
860                                 # debug("[FritzCallFBF] get wlan state: url: '" + url + "' parms: '" + parms + "'")
861                                 getPage(url,
862                                         method="POST",
863                                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
864                                         headers={
865                                                         'Content-Type': "application/x-www-form-urlencoded",
866                                                         'Content-Length': str(len(parms))},
867                                         postdata=parms).addCallback(lambda x:self._okSetWlanState(callback,x)).addErrback(self._errorGetInfo)
868                         else:
869                                 found = re.match('.*function WlanStateLed \(state\){.*?return StateLed\("(\d+)"\);\s*}', html, re.S)
870                                 if found:
871                                         # debug("[FritzCallFBF] _okGetInfo WlanState: " + found.group(1))
872                                         wlanState = [ found.group(1), 0, 0 ] # state, encryption, number of devices
873                                         found = re.match('.*var (?:g_)?encryption = "(\d+)";', html, re.S)
874                                         if found:
875                                                 # debug("[FritzCallFBF] _okGetInfo WlanEncrypt: " + found.group(1))
876                                                 wlanState[1] = found.group(1)
877
878                         return (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
879
880                 debug("[FritzCallFBF] _okGetInfo")
881                 info = readInfo(html)
882                 debug("[FritzCallFBF] _okGetInfo info: " + str(info))
883                 self.info = info
884                 if callback:
885                         callback(info)
886
887         def _okSetDect(self, callback, html):
888                 # debug("[FritzCallFBF] _okSetDect: " + html)
889                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
890                 if html.find('"dect_enabled": "1"') != -1:
891                         # debug("[FritzCallFBF] _okSetDect: dect_enabled")
892                         found = re.match('.*"dect_device_list":.*\[([^\]]*)\]', html, re.S)
893                         if found:
894                                 # debug("[FritzCallFBF] _okSetDect: dect_device_list: %s" %(found.group(1)))
895                                 entries = re.compile('"1"', re.S).findall(found.group(1))
896                                 dectActive = len(entries)
897                                 (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dummy, faxActive, rufumlActive) = self.info
898                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
899                                 debug("[FritzCallFBF] _okSetDect info: " + str(self.info))
900                 if callback:
901                         callback(self.info)
902
903         def _okSetConInfo(self, callback, html):
904                 # debug("[FritzCallFBF] _okSetConInfo: " + html)
905                 # found = re.match('.*"connection_status":"(\d+)".*"connection_ip":"([.\d]+)".*"connection_detail":"([^"]+)".*"connection_uptime":"([^"]+)"', html, re.S)
906                 found = re.match('.*"connection_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
907                 if found:
908                         # debug("[FritzCallFBF] _okSetConInfo: connection_ip: %s upTime: %s" %( found.group(1), found.group(2)))
909                         ipAddress = found.group(1)
910                         upTime = found.group(2)
911                         (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
912                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
913                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
914                 else:
915                         found = re.match('.*_ip": "([.\d]+)".*"connection_uptime": "([^"]+)"', html, re.S)
916                         if found:
917                                 # debug("[FritzCallFBF] _okSetConInfo: _ip: %s upTime: %s" %( found.group(1), found.group(2)))
918                                 ipAddress = found.group(1)
919                                 upTime = found.group(2)
920                                 (boxInfo, dummy, dummy, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
921                                 self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
922                                 debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
923                 if callback:
924                         callback(self.info)
925
926         def _okSetWlanState(self, callback, html):
927                 # debug("[FritzCallFBF] _okSetWlanState: " + html)
928                 found = re.match('.*"ap_enabled": "(\d+)"', html, re.S)
929                 if found:
930                         # debug("[FritzCallFBF] _okSetWlanState: ap_enabled: " + found.group(1))
931                         wlanState = [ found.group(1), None, None ]
932                         found = re.match('.*"encryption": "(\d+)"', html, re.S)
933                         if found:
934                                 # debug("[FritzCallFBF] _okSetWlanState: encryption: " + found.group(1))
935                                 wlanState[1] = found.group(1)
936                         found = re.match('.*"active_stations": "(\d+)"', html, re.S)
937                         if found:
938                                 # debug("[FritzCallFBF] _okSetWlanState: active_stations: " + found.group(1))
939                                 wlanState[2] = found.group(1)
940                         (boxInfo, upTime, ipAddress, dummy, dslState, tamActive, dectActive, faxActive, rufumlActive) = self.info
941                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
942                         debug("[FritzCallFBF] _okSetWlanState info: " + str(self.info))
943                 if callback:
944                         callback(self.info)
945
946         def _okSetDslState(self, callback, html):
947                 # debug("[FritzCallFBF] _okSetDslState: " + html)
948                 found = re.match('.*"dsl_carrier_state": "(\d+)"', html, re.S)
949                 if found:
950                         # debug("[FritzCallFBF] _okSetDslState: dsl_carrier_state: " + found.group(1))
951                         dslState = [ found.group(1), "" ]
952                         found = re.match('.*"dsl_ds_nrate": "(\d+)"', html, re.S)
953                         if found:
954                                 # debug("[FritzCallFBF] _okSetDslState: dsl_ds_nrate: " + found.group(1))
955                                 dslState[1] = found.group(1)
956                         found = re.match('.*"dsl_us_nrate": "(\d+)"', html, re.S)
957                         if found:
958                                 # debug("[FritzCallFBF] _okSetDslState: dsl_us_nrate: " + found.group(1))
959                                 dslState[1] = dslState[1] + '/' + found.group(1)
960                         (boxInfo, upTime, ipAddress, wlanState, dummy, tamActive, dectActive, faxActive, rufumlActive) = self.info
961                         self.info = (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive, faxActive, rufumlActive)
962                         debug("[FritzCallFBF] _okSetDslState info: " + str(self.info))
963                 if callback:
964                         callback(self.info)
965
966         def _errorGetInfo(self, error):
967                 debug("[FritzCallFBF] _errorGetInfo: %s" % (error))
968                 text = _("FRITZ!Box - Error getting status: %s") % error.getErrorMessage()
969                 self._notify(text)
970                 # linkP = open("/tmp/FritzCall_errorGetInfo.htm", "w")
971                 # linkP.write(error)
972                 # linkP.close()
973
974         def reset(self):
975                 self._login(self._reset)
976
977         def _reset(self, html):
978                 # 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
979                 if html:
980                         #===================================================================
981                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
982                         # if found:
983                         #       self._errorReset('Login: ' + found.group(1))
984                         #       return
985                         #===================================================================
986                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
987                         if start != -1:
988                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
989                                 self._errorReset('Login: ' + html[start, html.find('</p>', start)])
990                                 return
991                 if self._callScreen:
992                         self._callScreen.close()
993                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
994                 parms = urlencode({
995                         'getpage':'../html/reboot.html',
996                         'var:lang':'de',
997                         'var:pagename':'reset',
998                         'var:menu':'system',
999                         'logic:command/reboot':'../gateway/commands/saveconfig.html',
1000                         'sid':self._md5Sid
1001                         })
1002                 debug("[FritzCallFBF] _reset url: '" + url + "' parms: '" + parms + "'")
1003                 getPage(url,
1004                         method="POST",
1005                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1006                         headers={
1007                                         'Content-Type': "application/x-www-form-urlencoded",
1008                                         'Content-Length': str(len(parms))},
1009                         postdata=parms)
1010
1011         def _okReset(self, html): #@UnusedVariable # pylint: disable=W0613
1012                 debug("[FritzCallFBF] _okReset")
1013
1014         def _errorReset(self, error):
1015                 debug("[FritzCallFBF] _errorReset: %s" % (error))
1016                 text = _("FRITZ!Box - Error resetting: %s") % error.getErrorMessage()
1017                 self._notify(text)
1018
1019         def readBlacklist(self):
1020                 self._login(self._readBlacklist)
1021                 
1022         def _readBlacklist(self, html):
1023                 if html:
1024                         #===================================================================
1025                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1026                         # if found:
1027                         #       self._errorBlacklist('Login: ' + found.group(1))
1028                         #       return
1029                         #===================================================================
1030                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1031                         if start != -1:
1032                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1033                                 self._errorBlacklist('Login: ' + html[start, html.find('</p>', start)])
1034                                 return
1035                 # http://fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=sperre
1036                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1037                 parms = urlencode({
1038                         'getpage':'../html/de/menus/menu2.html',
1039                         'var:lang':'de',
1040                         'var:pagename':'sperre',
1041                         'var:menu':'fon',
1042                         'sid':self._md5Sid
1043                         })
1044                 debug("[FritzCallFBF] _readBlacklist url: '" + url + "' parms: '" + parms + "'")
1045                 getPage(url,
1046                         method="POST",
1047                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1048                         headers={
1049                                         'Content-Type': "application/x-www-form-urlencoded",
1050                                         'Content-Length': str(len(parms))},
1051                         postdata=parms).addCallback(self._okBlacklist).addErrback(self._errorBlacklist)
1052
1053         def _okBlacklist(self, html):
1054                 debug("[FritzCallFBF] _okBlacklist")
1055                 entries = re.compile('<script type="text/javascript">document.write\(Tr(Out|In)\("\d+", "(\d+)", "\w*"\)\);</script>', re.S).finditer(html)
1056                 self.blacklist = ([], [])
1057                 for entry in entries:
1058                         if entry.group(1) == "In":
1059                                 self.blacklist[0].append(entry.group(2))
1060                         else:
1061                                 self.blacklist[1].append(entry.group(2))
1062                 debug("[FritzCallFBF] _okBlacklist: %s" % repr(self.blacklist))
1063
1064         def _errorBlacklist(self, error):
1065                 debug("[FritzCallFBF] _errorBlacklist: %s" % (error))
1066                 text = _("FRITZ!Box - Error getting blacklist: %s") % error.getErrorMessage()
1067                 self._notify(text)
1068
1069 #===============================================================================
1070 #       def hangup(self):
1071 #               ''' hangup call on port; not used for now '''
1072 #               url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1073 #               parms = urlencode({
1074 #                       'id':'uiPostForm',
1075 #                       'name':'uiPostForm',
1076 #                       'login:command/password': config.plugins.FritzCall.password.value,
1077 #                       'telcfg:settings/UseClickToDial':'1',
1078 #                       'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
1079 #                       'telcfg:command/Hangup':'',
1080 #                       'sid':self._md5Sid
1081 #                       })
1082 #               debug("[FritzCallFBF] hangup url: '" + url + "' parms: '" + parms + "'")
1083 #               getPage(url,
1084 #                       method="POST",
1085 #                       headers={
1086 #                                       'Content-Type': "application/x-www-form-urlencoded",
1087 #                                       'Content-Length': str(len(parms))},
1088 #                       postdata=parms)
1089 #===============================================================================
1090
1091 class FritzCallFBF_05_27:
1092         def __init__(self):
1093                 debug("[FritzCallFBF_05_27] __init__")
1094                 self._callScreen = None
1095                 self._md5LoginTimestamp = None
1096                 self._md5Sid = '0000000000000000'
1097                 self._callTimestamp = 0
1098                 self._callList = []
1099                 self._callType = config.plugins.FritzCall.fbfCalls.value
1100                 self._phoneBookID = '0'
1101                 self.info = None # (boxInfo, upTime, ipAddress, wlanState, dslState, tamActive, dectActive)
1102                 self.getInfo(None)
1103                 self.blacklist = ([], [])
1104                 self.readBlacklist()
1105                 self.phonebook = None
1106
1107         def _notify(self, text):
1108                 debug("[FritzCallFBF_05_27] notify: " + text)
1109                 self._md5LoginTimestamp = None
1110                 if self._callScreen:
1111                         debug("[FritzCallFBF_05_27] notify: try to close callScreen")
1112                         self._callScreen.close()
1113                         self._callScreen = None
1114                 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1115                         
1116         def _login(self, callback=None):
1117                 debug("[FritzCallFBF_05_27] _login")
1118                 if self._callScreen:
1119                         self._callScreen.updateStatus(_("login"))
1120                 if self._md5LoginTimestamp and ((time.time() - self._md5LoginTimestamp) < float(9.5*60)) and self._md5Sid != '0000000000000000': # new login after 9.5 minutes inactivity 
1121                         debug("[FritzCallFBF_05_27] _login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
1122                         self._md5LoginTimestamp = time.time()
1123                         callback(None)
1124                 else:
1125                         debug("[FritzCallFBF_05_27] _login: not logged in or outdated login")
1126                         # http://fritz.box/cgi-bin/webcm?getpage=../html/login_sid.xml
1127                         parms = urlencode({'getpage':'../html/login_sid.xml'})
1128                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
1129                         debug("[FritzCallFBF_05_27] _login: '" + url + "' parms: '" + parms + "'")
1130                         getPage(url,
1131                                 method="POST",
1132                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1133                                                 }, postdata=parms).addCallback(lambda x: self._md5Login(callback,x)).addErrback(self._errorLogin)
1134
1135         def _md5Login(self, callback, sidXml):
1136                 def buildResponse(challenge, text):
1137                         debug("[FritzCallFBF_05_27] _md5Login7buildResponse: challenge: " + challenge + ' text: ' + text)
1138                         text = (challenge + '-' + text).decode('utf-8','ignore').encode('utf-16-le')
1139                         for i in range(len(text)):
1140                                 if ord(text[i]) > 255:
1141                                         text[i] = '.'
1142                         md5 = hashlib.md5()
1143                         md5.update(text)
1144                         debug("[FritzCallFBF_05_27] md5Login/buildResponse: " + md5.hexdigest())
1145                         return challenge + '-' + md5.hexdigest()
1146
1147                 debug("[FritzCallFBF_05_27] _md5Login")
1148                 found = re.match('.*<SID>([^<]*)</SID>', sidXml, re.S)
1149                 if found:
1150                         self._md5Sid = found.group(1)
1151                         debug("[FritzCallFBF_05_27] _md5Login: SID "+ self._md5Sid)
1152                 else:
1153                         debug("[FritzCallFBF_05_27] _md5Login: no sid! That must be an old firmware.")
1154                         self._errorLogin('No sid?!?')
1155                         return
1156
1157                 debug("[FritzCallFBF_05_27] _md5Login: renew timestamp: " + time.ctime(self._md5LoginTimestamp) + " time: " + time.ctime())
1158                 self._md5LoginTimestamp = time.time()
1159                 if sidXml.find('<iswriteaccess>0</iswriteaccess>') != -1:
1160                         debug("[FritzCallFBF_05_27] _md5Login: logging in")
1161                         found = re.match('.*<Challenge>([^<]*)</Challenge>', sidXml, re.S)
1162                         if found:
1163                                 challenge = found.group(1)
1164                                 debug("[FritzCallFBF_05_27] _md5Login: challenge " + challenge)
1165                         else:
1166                                 challenge = None
1167                                 debug("[FritzCallFBF_05_27] _md5Login: login necessary and no challenge! That is terribly wrong.")
1168                         parms = urlencode({
1169                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
1170                                                         'login:command/response': buildResponse(challenge, config.plugins.FritzCall.password.value),
1171                                                         })
1172                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
1173                         debug("[FritzCallFBF_05_27] _md5Login: '" + url + "' parms: '" + parms + "'")
1174                         getPage(url,
1175                                 method="POST",
1176                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1177                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1178                                                 }, postdata=parms).addCallback(self._gotPageLogin).addCallback(callback).addErrback(self._errorLogin)
1179                 elif callback: # we assume value 1 here, no login necessary
1180                         debug("[FritzCallFBF_05_27] _md5Login: no login necessary")
1181                         callback(None)
1182
1183         def _gotPageLogin(self, html):
1184                 if self._callScreen:
1185                         self._callScreen.updateStatus(_("login verification"))
1186                 debug("[FritzCallFBF_05_27] _gotPageLogin: verify login")
1187                 start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1188                 if start != -1:
1189                         start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1190                         text = _("FRITZ!Box - Error logging in\n\n") + html[start : html.find('</p>', start)]
1191                         self._notify(text)
1192                 else:
1193                         if self._callScreen:
1194                                 self._callScreen.updateStatus(_("login ok"))
1195
1196                 found = re.match('.*<input type="hidden" name="sid" value="([^\"]*)"', html, re.S)
1197                 if found:
1198                         self._md5Sid = found.group(1)
1199                         debug("[FritzCallFBF_05_27] _gotPageLogin: found sid: " + self._md5Sid)
1200
1201         def _errorLogin(self, error):
1202                 global fritzbox
1203                 debug("[FritzCallFBF_05_27] _errorLogin: %s" % (error))
1204                 text = _("FRITZ!Box - Error logging in: %s\nDisabling plugin.") % error.getErrorMessage()
1205                 # config.plugins.FritzCall.enable.value = False
1206                 fritzbox = None
1207                 self._notify(text)
1208
1209         def _logout(self):
1210                 if self._md5LoginTimestamp:
1211                         self._md5LoginTimestamp = None
1212                         parms = urlencode({
1213                                                         'getpage':'../html/de/menus/menu2.html', # 'var:pagename':'home', 'var:menu':'home', 
1214                                                         'login:command/logout':'bye bye Fritz'
1215                                                         })
1216                         url = "http://%s/cgi-bin/webcm" % (config.plugins.FritzCall.hostname.value)
1217                         debug("[FritzCallFBF_05_27] logout: '" + url + "' parms: '" + parms + "'")
1218                         getPage(url,
1219                                 method="POST",
1220                                 agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1221                                 headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1222                                                 }, postdata=parms).addErrback(self._errorLogout)
1223
1224         def _errorLogout(self, error):
1225                 debug("[FritzCallFBF_05_27] _errorLogout: %s" % (error))
1226                 text = _("FRITZ!Box - Error logging out: %s") % error.getErrorMessage()
1227                 self._notify(text)
1228
1229         def loadFritzBoxPhonebook(self, phonebook):
1230                 self.phonebook = phonebook
1231                 self._login(self._loadFritzBoxPhonebook)
1232
1233         def _loadFritzBoxPhonebook(self, html):
1234                 # Firmware 05.27 onwards
1235                 # first, let us get the charset
1236                 if html:
1237                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1238                         if start != -1:
1239                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1240                                 self._errorLoad('Login: ' + html[start, html.find('</p>', start)])
1241                                 return
1242                 # http://192.168.178.1/fon_num/fonbook_list.lua?sid=2faec13b0000f3a2
1243                 parms = urlencode({
1244                                                 'sid':self._md5Sid,
1245                                                 })
1246                 url = "http://%s/fon_num/fonbook_list.lua" % (config.plugins.FritzCall.hostname.value)
1247                 debug("[FritzCallFBF_05_27] _loadFritzBoxPhonebookNew: '" + url + "' parms: '" + parms + "'")
1248                 getPage(url,
1249                         method="POST",
1250                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1251                         headers={'Content-Type': "application/x-www-form-urlencoded", 'Content-Length': str(len(parms))
1252                                         }, postdata=parms).addCallback(self._parseFritzBoxPhonebook).addErrback(self._errorLoad)
1253
1254         def _parseFritzBoxPhonebook(self, html):
1255                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew")
1256                 #=======================================================================
1257                 # found = re.match('.*<input type="hidden" name="telcfg:settings/Phonebook/Books/Name\d+" value="[Dd]reambox" id="uiPostPhonebookName\d+" disabled>\s*<input type="hidden" name="telcfg:settings/Phonebook/Books/Id\d+" value="(\d+)" id="uiPostPhonebookId\d+" disabled>', html, re.S)
1258                 # if found:
1259                 #       phoneBookID = found.group(1)
1260                 #       debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: found dreambox phonebook with id: " + phoneBookID)
1261                 #       if self._phoneBookID != phoneBookID:
1262                 #               self._phoneBookID = phoneBookID
1263                 #               debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: reload phonebook")
1264                 #               self._loadFritzBoxPhonebook(None) # reload with dreambox phonebook
1265                 #               return
1266                 #=======================================================================
1267
1268                 # first, let us get the charset
1269                 found = re.match('.*<meta http-equiv=content-type content="text/html; charset=([^"]*)">', html, re.S)
1270                 if found:
1271                         charset = found.group(1)
1272                         debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: found charset: " + charset)
1273                         html = html2unicode(html.replace(chr(0xf6),'').decode(charset)).encode('utf-8')
1274                 else: # this is kind of emergency conversion...
1275                         try:
1276                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: try charset utf-8")
1277                                 charset = 'utf-8'
1278                                 html = html2unicode(html.decode('utf-8')).encode('utf-8') # this looks silly, but has to be
1279                         except UnicodeDecodeError:
1280                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: try charset iso-8859-1")
1281                                 charset = 'iso-8859-1'
1282                                 html = html2unicode(html.decode('iso-8859-1')).encode('utf-8') # this looks silly, but has to be
1283
1284                 # cleanout hrefs
1285                 html = re.sub("<a href[^>]*>", "", html)
1286                 html = re.sub("</a>", "", html)
1287                 linkP = open("/tmp/FritzCall_Phonebook.htm", "w")
1288                 linkP.write(html)
1289                 linkP.close()
1290
1291                 if html.find('class="zebra_reverse"') != -1:
1292                         debug("[FritzCallFBF_05_27] Found new 7390 firmware")
1293                         # <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>
1294                         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)
1295                         entries = entrymask.finditer(html)
1296                         for found in entries:
1297                                 # debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: processing entry for '''%s'''" % (found.group(1)))
1298                                 name = found.group(1)
1299                                 thisnumbers = found.group(2).split("<br>")
1300                                 thistypes = found.group(3).split("<br>")
1301                                 thiscodes = found.group(4).split("<br>")
1302                                 thisvanitys = found.group(5).split("<br>")
1303                                 for i in range(len(thisnumbers)):
1304                                         thisnumber = cleanNumber(thisnumbers[i])
1305                                         if self.phonebook.phonebook.has_key(thisnumber):
1306                                                 debug("[FritzCallFBF_05_27] Ignoring '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (name, thisnumber))
1307                                                 continue
1308
1309                                         if not thisnumbers[i]:
1310                                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: Ignoring entry with empty number for '''%s'''" % (name))
1311                                                 continue
1312                                         else:
1313                                                 thisname = name
1314                                                 if config.plugins.FritzCall.showType.value and thistypes[i]:
1315                                                         thisname = thisname + " (" + thistypes[i] + ")"
1316                                                 if config.plugins.FritzCall.showShortcut.value and thiscodes[i]:
1317                                                         thisname = thisname + ", " + _("Shortcut") + ": " + thiscodes[i]
1318                                                 if config.plugins.FritzCall.showVanity.value and thisvanitys[i]:
1319                                                         thisname = thisname + ", " + _("Vanity") + ": " + thisvanitys[i]
1320         
1321                                                 debug("[FritzCallFBF_05_27] _parseFritzBoxPhonebookNew: Adding '''%s''' with '''%s''' from FRITZ!Box Phonebook!" % (thisname.strip(), thisnumber))
1322                                                 # Beware: strings in phonebook.phonebook have to be in utf-8!
1323                                                 self.phonebook.phonebook[thisnumber] = thisname
1324                 else:
1325                         self._notify(_("Could not parse FRITZ!Box Phonebook entry"))
1326
1327         def _errorLoad(self, error):
1328                 debug("[FritzCallFBF_05_27] _errorLoad: %s" % (error))
1329                 text = _("FRITZ!Box - Could not load phonebook: %s") % error.getErrorMessage()
1330                 self._notify(text)
1331
1332         def getCalls(self, callScreen, callback, callType):
1333                 #
1334                 # FW 05.27 onwards
1335                 #
1336                 self._callScreen = callScreen
1337                 self._callType = callType
1338                 debug("[FritzCallFBF_05_27] _getCalls1New")
1339                 if self._callScreen:
1340                         self._callScreen.updateStatus(_("finishing"))
1341                 # http://192.168.178.1/fon_num/foncalls_list.lua?sid=da78ab0797197dc7
1342                 parms = urlencode({'sid':self._md5Sid})
1343                 url = "http://%s/fon_num/foncalls_list.lua?%s" % (config.plugins.FritzCall.hostname.value, parms)
1344                 getPage(url).addCallback(lambda x:self._gotPageCallsNew(callback, x)).addErrback(self._errorCalls)
1345
1346         def _gotPageCallsNew(self, callback, html=""):
1347
1348                 debug("[FritzCallFBF_05_27] _gotPageCallsNew")
1349                 if self._callScreen:
1350                         self._callScreen.updateStatus(_("preparing"))
1351
1352                 callListL = []
1353                 if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
1354                         filtermsns = map(lambda x: x.strip(), config.plugins.FritzCall.filtermsn.value.split(","))
1355                         debug("[FritzCallFBF_05_27] _gotPageCallsNew: filtermsns %s" % (repr(filtermsns)))
1356
1357                 #=======================================================================
1358                 # linkP = open("/tmp/FritzCall_Calllist.htm", "w")
1359                 # linkP.write(html)
1360                 # linkP.close()
1361                 #=======================================================================
1362                 # 1: direct; 2: date; 3: Rufnummer; 4: Name; 5: Nebenstelle; 6: Eigene Rufnumme lang; 7: Eigene Rufnummer; 8: Dauer
1363                 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)
1364                 entries = entrymask.finditer(html)
1365                 for found in entries:
1366                         if found.group(1) == "call_in":
1367                                 direct = FBF_IN_CALLS
1368                         elif found.group(1) == "call_out":
1369                                 direct = FBF_OUT_CALLS
1370                         elif found.group(1) == "call_in_fail":
1371                                 direct = FBF_MISSED_CALLS
1372                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: direct: " + direct)
1373                         if direct != self._callType and "." != self._callType:
1374                                 continue
1375
1376                         date = found.group(2)
1377                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: date: " + date)
1378                         length = found.group(8)
1379                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: len: " + length)
1380                         if config.plugins.FritzCall.phonebook.value:
1381                                 remote = found.group(4)
1382                                 if remote and not remote.isdigit():
1383                                         remote = resolveNumber(found.group(3), remote + " (FBF)", self.phonebook)
1384                                 else:
1385                                         remote = resolveNumber(found.group(3), "", self.phonebook)
1386                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: remote. " + remote)
1387                         here = found.group(7)
1388                         #===================================================================
1389                         # start = here.find('Internet: ')
1390                         # if start != -1:
1391                         #       start += len('Internet: ')
1392                         #       here = here[start:]
1393                         # else:
1394                         #       here = line[5]
1395                         #===================================================================
1396                         if config.plugins.FritzCall.filter.value and config.plugins.FritzCall.filterCallList.value:
1397                                 # debug("[FritzCallFBF_05_27] _gotPageCalls: check %s" % (here))
1398                                 if here not in filtermsns:
1399                                         # debug("[FritzCallFBF_05_27] _gotPageCalls: skip %s" % (here))
1400                                         continue
1401                         here = resolveNumber(here, found.group(6), self.phonebook)
1402                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: here: " + here)
1403
1404                         number = stripCbCPrefix(found.group(3), config.plugins.FritzCall.country.value)
1405                         if config.plugins.FritzCall.prefix.value and number and number[0] != '0':               # should only happen for outgoing
1406                                 number = config.plugins.FritzCall.prefix.value + number
1407                         # debug("[FritzCallFBF_05_27] _gotPageCallsNew: number: " + number)
1408                         debug("[FritzCallFBF_05_27] _gotPageCallsNew: append: %s" % repr((number, date, direct, remote, length, here)) )
1409                         callListL.append((number, date, direct, remote, length, here))
1410
1411                 if callback:
1412                         # debug("[FritzCallFBF_05_27] _gotPageCalls call callback with\n" + text
1413                         callback(callListL)
1414                 self._callScreen = None
1415
1416         def _errorCalls(self, error):
1417                 debug("[FritzCallFBF_05_27] _errorCalls: %s" % (error))
1418                 text = _("FRITZ!Box - Could not load calls: %s") % error.getErrorMessage()
1419                 self._notify(text)
1420
1421         def dial(self, number):
1422                 ''' initiate a call to number '''
1423                 self._login(lambda x: self._dial(number, x))
1424                 
1425         def _dial(self, number, html):
1426                 if html:
1427                         #===================================================================
1428                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1429                         # if found:
1430                         #       self._errorDial('Login: ' + found.group(1))
1431                         #       return
1432                         #===================================================================
1433                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1434                         if start != -1:
1435                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1436                                 self._errorDial('Login: ' + html[start, html.find('</p>', start)])
1437                                 return
1438                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1439                 parms = urlencode({
1440                         'getpage':'../html/de/menus/menu2.html',
1441                         'var:pagename':'fonbuch',
1442                         'var:menu':'home',
1443                         'telcfg:settings/UseClickToDial':'1',
1444                         'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
1445                         'telcfg:command/Dial':number,
1446                         'sid':self._md5Sid
1447                         })
1448                 debug("[FritzCallFBF_05_27] dial url: '" + url + "' parms: '" + parms + "'")
1449                 getPage(url,
1450                         method="POST",
1451                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1452                         headers={
1453                                         'Content-Type': "application/x-www-form-urlencoded",
1454                                         'Content-Length': str(len(parms))},
1455                         postdata=parms).addCallback(self._okDial).addErrback(self._errorDial)
1456
1457         def _okDial(self, html): #@UnusedVariable # pylint: disable=W0613
1458                 debug("[FritzCallFBF_05_27] okDial")
1459
1460         def _errorDial(self, error):
1461                 debug("[FritzCallFBF_05_27] errorDial: $s" % error)
1462                 text = _("FRITZ!Box - Dialling failed: %s") % error.getErrorMessage()
1463                 self._notify(text)
1464
1465         def changeWLAN(self, statusWLAN):
1466                 ''' get status info from FBF '''
1467                 debug("[FritzCallFBF_05_27] changeWLAN start")
1468                 Notifications.AddNotification(MessageBox, _("not yet implemented"), type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1469                 
1470         def _changeWLAN(self, statusWLAN, html):
1471                 return
1472
1473         def _okChangeWLAN(self, html): #@UnusedVariable # pylint: disable=W0613
1474                 debug("[FritzCallFBF_05_27] _okChangeWLAN")
1475
1476         def _errorChangeWLAN(self, error):
1477                 debug("[FritzCallFBF_05_27] _errorChangeWLAN: $s" % error)
1478                 text = _("FRITZ!Box - Failed changing WLAN: %s") % error.getErrorMessage()
1479                 self._notify(text)
1480
1481         def changeMailbox(self, whichMailbox):
1482                 ''' switch mailbox on/off '''
1483                 debug("[FritzCallFBF_05_27] changeMailbox start: " + str(whichMailbox))
1484                 Notifications.AddNotification(MessageBox, _("not yet implemented"), type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1485
1486         def _changeMailbox(self, whichMailbox, html):
1487                 return
1488
1489         def _okChangeMailbox(self, html): #@UnusedVariable # pylint: disable=W0613
1490                 debug("[FritzCallFBF_05_27] _okChangeMailbox")
1491
1492         def _errorChangeMailbox(self, error):
1493                 debug("[FritzCallFBF_05_27] _errorChangeMailbox: $s" % error)
1494                 text = _("FRITZ!Box - Failed changing Mailbox: %s") % error.getErrorMessage()
1495                 self._notify(text)
1496
1497         def getInfo(self, callback):
1498                 ''' get status info from FBF '''
1499                 debug("[FritzCallFBF_05_27] getInfo")
1500                 # Notifications.AddNotification(MessageBox, _("not yet implemented"), type=MessageBox.TYPE_ERROR, timeout=config.plugins.FritzCall.timeout.value)
1501                 
1502         def _getInfo(self, callback, html):
1503                 return
1504
1505         def _okGetInfo(self, callback, html):
1506                 return
1507
1508         def _okSetDect(self, callback, html):
1509                 return
1510         
1511         def _okSetConInfo(self, callback, html):
1512                 return
1513
1514         def _okSetWlanState(self, callback, html):
1515                 return
1516
1517         def _okSetDslState(self, callback, html):
1518                 return
1519
1520         def _errorGetInfo(self, error):
1521                 debug("[FritzCallFBF_05_27] _errorGetInfo: %s" % (error))
1522                 text = _("FRITZ!Box - Error getting status: %s") % error.getErrorMessage()
1523                 self._notify(text)
1524                 return
1525
1526         def reset(self):
1527                 self._login(self._reset)
1528
1529         def _reset(self, html):
1530                 # 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
1531                 if html:
1532                         #===================================================================
1533                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1534                         # if found:
1535                         #       self._errorReset('Login: ' + found.group(1))
1536                         #       return
1537                         #===================================================================
1538                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1539                         if start != -1:
1540                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1541                                 self._errorReset('Login: ' + html[start, html.find('</p>', start)])
1542                                 return
1543                 if self._callScreen:
1544                         self._callScreen.close()
1545                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1546                 parms = urlencode({
1547                         'getpage':'../html/reboot.html',
1548                         'var:lang':'de',
1549                         'var:pagename':'reset',
1550                         'var:menu':'system',
1551                         'logic:command/reboot':'../gateway/commands/saveconfig.html',
1552                         'sid':self._md5Sid
1553                         })
1554                 debug("[FritzCallFBF_05_27] _reset url: '" + url + "' parms: '" + parms + "'")
1555                 getPage(url,
1556                         method="POST",
1557                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1558                         headers={
1559                                         'Content-Type': "application/x-www-form-urlencoded",
1560                                         'Content-Length': str(len(parms))},
1561                         postdata=parms)
1562
1563         def _okReset(self, html): #@UnusedVariable # pylint: disable=W0613
1564                 debug("[FritzCallFBF_05_27] _okReset")
1565
1566         def _errorReset(self, error):
1567                 debug("[FritzCallFBF_05_27] _errorReset: %s" % (error))
1568                 text = _("FRITZ!Box - Error resetting: %s") % error.getErrorMessage()
1569                 self._notify(text)
1570
1571         def readBlacklist(self):
1572                 self._login(self._readBlacklist)
1573                 
1574         def _readBlacklist(self, html):
1575                 if html:
1576                         #===================================================================
1577                         # found = re.match('.*<p class="errorMessage">FEHLER:&nbsp;([^<]*)</p>', html, re.S)
1578                         # if found:
1579                         #       self._errorBlacklist('Login: ' + found.group(1))
1580                         #       return
1581                         #===================================================================
1582                         start = html.find('<p class="errorMessage">FEHLER:&nbsp;')
1583                         if start != -1:
1584                                 start = start + len('<p class="errorMessage">FEHLER:&nbsp;')
1585                                 self._errorBlacklist('Login: ' + html[start, html.find('</p>', start)])
1586                                 return
1587                 # http://fritz.box/cgi-bin/webcm?getpage=../html/de/menus/menu2.html&var:lang=de&var:menu=fon&var:pagename=sperre
1588                 url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1589                 parms = urlencode({
1590                         'getpage':'../html/de/menus/menu2.html',
1591                         'var:lang':'de',
1592                         'var:pagename':'sperre',
1593                         'var:menu':'fon',
1594                         'sid':self._md5Sid
1595                         })
1596                 debug("[FritzCallFBF_05_27] _readBlacklist url: '" + url + "' parms: '" + parms + "'")
1597                 getPage(url,
1598                         method="POST",
1599                         agent="Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5",
1600                         headers={
1601                                         'Content-Type': "application/x-www-form-urlencoded",
1602                                         'Content-Length': str(len(parms))},
1603                         postdata=parms).addCallback(self._okBlacklist).addErrback(self._errorBlacklist)
1604
1605         def _okBlacklist(self, html):
1606                 debug("[FritzCallFBF_05_27] _okBlacklist")
1607                 entries = re.compile('<script type="text/javascript">document.write\(Tr(Out|In)\("\d+", "(\d+)", "\w*"\)\);</script>', re.S).finditer(html)
1608                 self.blacklist = ([], [])
1609                 for entry in entries:
1610                         if entry.group(1) == "In":
1611                                 self.blacklist[0].append(entry.group(2))
1612                         else:
1613                                 self.blacklist[1].append(entry.group(2))
1614                 debug("[FritzCallFBF_05_27] _okBlacklist: %s" % repr(self.blacklist))
1615
1616         def _errorBlacklist(self, error):
1617                 debug("[FritzCallFBF_05_27] _errorBlacklist: %s" % (error))
1618                 text = _("FRITZ!Box - Error getting blacklist: %s") % error.getErrorMessage()
1619                 self._notify(text)
1620
1621 #===============================================================================
1622 #       def hangup(self):
1623 #               ''' hangup call on port; not used for now '''
1624 #               url = "http://%s/cgi-bin/webcm" % config.plugins.FritzCall.hostname.value
1625 #               parms = urlencode({
1626 #                       'id':'uiPostForm',
1627 #                       'name':'uiPostForm',
1628 #                       'login:command/password': config.plugins.FritzCall.password.value,
1629 #                       'telcfg:settings/UseClickToDial':'1',
1630 #                       'telcfg:settings/DialPort':config.plugins.FritzCall.extension.value,
1631 #                       'telcfg:command/Hangup':'',
1632 #                       'sid':self._md5Sid
1633 #                       })
1634 #               debug("[FritzCallFBF_05_27] hangup url: '" + url + "' parms: '" + parms + "'")
1635 #               getPage(url,
1636 #                       method="POST",
1637 #                       headers={
1638 #                                       'Content-Type': "application/x-www-form-urlencoded",
1639 #                                       'Content-Length': str(len(parms))},
1640 #                       postdata=parms)
1641 #===============================================================================