kbr / fritzconnection

Python-Tool to communicate with the AVM Fritz!Box by the TR-064 protocol and the AHA-HTTP-Interface
MIT License
304 stars 59 forks source link

fritzphonebook fails on PhoneBook entries without proper name #79

Open avierck opened 3 years ago

avierck commented 3 years ago

I have no idea why, but ever since updating to Fritz!OS to 07.21 the phonebook on my Fritz!Box 7590 apparently contains an entry for an internal number (3) that has no username associated with it. While this doesn't even show up on the regular web interface, the iOS Fritz! Fon app is at least able to hand this and shows an entry `(null) 3` under the internal numbers.

Now the issue is that fritzphonebook -a is unable to handle such missing names, as seen here:

Traceback (most recent call last):
  File "/Users/myusername/.pyenv/versions/3.8.6/bin/fritzphonebook", line 8, in <module>
    sys.exit(main())
  File "/Users/myusername/.pyenv/versions/3.8.6/lib/python3.8/site-packages/fritzconnection/cli/fritzphonebook.py", line 70, in main
    print_phonebooks(fpb)
  File "/Users/myusername/.pyenv/versions/3.8.6/lib/python3.8/site-packages/fritzconnection/cli/fritzphonebook.py", line 22, in print_phonebooks
    print(f"{name:<30}{', '.join(numbers)}")
TypeError: unsupported format string passed to NoneType.__format__

Which could be solved either by forcing python to interpret name as string type on this line either by changing it to :

        print(f"{name!s:<30}{', '.join(numbers)}")

or

        print(f"{str(name):<30}{', '.join(numbers)}")

which both yield an entry None **3.

Although it would of course be generally more stable if this library would use a more stable type-checking approach, at least by prefacing that line with if isinstance(name, str):


On a related note, has anyone tried using the DeletePhonebookEntryUID method yet? So far I managed to get the raw phonebook data (and thus, I think, got the unique id for my messed-up phonebook entry) via establishing a new FritzConnection and then calling .call_action('X_AVM-DE_OnTel1','GetPhonebook',NewPhonebookId=0) on it, which yields:

   <contact>
   <category>
    0
   </category>
   <person>
    <realname>
    </realname>
   </person>
   <uniqueid>
    14
   </uniqueid>
   <telephony>
    <services>
    </services>
    <!-- numbers:1-->
    <number prio="1" type="intern" vanity="">
     **3
    </number>
    <!-- idx:0 -->
    <!-- ringtoneidx:nil -->
   </telephony>
  </contact>

But I'm unsure how to properly call DeletePhonebookEntryUID and I don't want to mess things up even more.

avierck commented 3 years ago

Small update:

I've now tried to get more info about that specific contact entry like this:

FB_ADDR = 'redacted'
FB_USER = 'redacted'
FB_PASS = 'redacted'

import requests
from lxml import etree
from fritzconnection import FritzConnection as fco

conn = fco(address=FB_ADDR,user=FB_USER,password=FB_PASS)

def get_info_uid(pb=0,contact_uid=18):
    pbl = conn.call_action('X_AVM-DE_OnTel1','GetPhonebookEntryUID', NewPhonebookId=pb, NewPhonebookEntryUniqueID=contact_uid)
    return pbl['NewPhonebookEntryData']

def get_whole_phonebook(pb=0):
    pbl = conn.call_action('X_AVM-DE_OnTel1','GetPhonebook',NewPhonebookId=pb)
    url = pbl['NewPhonebookURL']

    content = requests.get(url)

    xml = content.text.encode('utf-8')
    parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8', remove_comments=True)

    root = etree.fromstring(xml, parser=parser)
    tree = etree.ElementTree(root)

    for ci, c in enumerate(root.iterfind(".//phonebook/contact")):
        rn = c.findall('.//person/realName')[0].text
        uid = c.findall('.//uniqueid')[0].text
        ph = c.findall('telephony/number')[0].text
        print(c.findall('telephony/number'))
        print(f"[{ci!s:>3}] {uid!s:<3} - {rn!s:<30} {ph!s:<30}")

get_whole_phonebook()

Which gives not only names and numbers, but also the s so that one can attempt to retrieve specific information over the GetPhonebookEntryUID service like:

print(get_info_uid(contact_uid=28)) 

Turns out, however, that I can only retrieve data for "normal" phonebook entries, not the internal ones. So I fear that attempting to use DeletePhonebookEntryUID will also fail.

bufemc commented 3 years ago

Hey @avierck - I also have running the 7.21 on my 7490, but don't have a record for 3. Just 1 for the fax device. Maybe @kbr has to add something in case a number has no name assigned?

I had also to investigate how to add numbers to the phone book, but could finally manage it - see my code at https://github.com/bufemc/a1fbox - the phonebook class. I guess DeletePhonebookEntryUID should be nearly the same. Maybe helpful is this resource, too: https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/X_contactSCPD.pdf

Another thing - I currently try to get the internal numbers of "Telefonie - Telefoniegeraete".. any ideas? I will create a new ticket for that.

kbr commented 3 years ago

Well, I don't have done any reegineering of undocumented features – or at least I don't know about the documentation – and therefore also don't know what AVM is doing there. However, there is an improved phonebook library module in the pipeline – may be for version 1.5 – better handling to insert or delete phonebook entries. Unfortunately I'm currently short in time, regardless of the lockdown.

bufemc commented 3 years ago

That would be nice as I just created some kind of XML snippet workaround to add phone numbers, but I guess you could do it the smarter way. Beside of Insert/Delete I would even suggest the full CRUD.. Create, Retrieve, Update, Delete. Why? Scenario: I don't know why, but in my phonebook (whitelist) there are a lot of entries "Name," and I would like to get rid of this nasty "," by updating all the entries. Kleines Wunschkonzert quasi.. Ist denn schon wieder Weihnachten? o;-)