Für uns iPhone- und Mac-Jünger ist die Mac OS X Adressverwaltung wunderbar integriert. Leider fehlte mir bisher eine Möglichkeit zum abgleich des Adressbuchs mit der Fritzbox. Das folgende Python-Programm schrieb ich, um dem abhilfe zu verschaffen. Ich suche noch immer nach einer Möglichkeit, das Programm per automatischen job einzuplanen – Hilfe wäre mir hier sehr willkommen!?
Das Python-Script zum ausführen und
Download: iPyFritz
Der Aufruf erfolgt nach dem entpacken per Doppelklick oder im Terminal mithilfe folgenden Befehls:
Dies erzeugt die Datei: myfritz.adressbuch.xml – welche in der Fritzbox unter “Telefonbuch – Wiederherstellen” eingespielt werden kann. VORSICHT – ALLE ALTEN EINTRÄGE WERDEN GELÖSCHT!!!
Für alle die ein paar Euro ausgeben möchten und können, könnte das hier interessanter sein: fritz.mac Suite. Wobei es in meinen Tests nicht die gleichen Ziele wie mein Adressbuch-exporter hat und die Fax-Anbindung an der 7370 zu abbrüchen führte…
Und hier das Coding / Thx a lot to programmish ); for releasing his snippet under public domain.
Update 11.04.2010
Nur Nickname-Fehler korrigiert
Mazo-Kategorie Update 17.04.2010
Das Script setzt das “Wichtig”-Flag für Personen die einen Nickname gepflegt haben im Adressbuch.
Update 23.04.2010
weitere Alternativen: Fakes Blog
Update 12.05.2010
Suche immer noch nach einer Möglichkeit per launchd (cron alternative) das Adressbuch automatisch zu syncen… Über einen Tipp würde ich mich sehr freuen.
import re
from xml.dom.minidom import *
def addressBookToList():
"""
Copyright (c) 2010, J.Rumpf, www.web-dreamer.de / BSD Licence
All rights reserved.
original found on: http://www.programmish.com/?p=26 released under public domain
Read the current user's AddressBook database, converting each person
in the address book into a Dictionary of values. Some values (addresses,
phone numbers, email, etc) can have multiple values, in which case a
list of all of those values is stored. The result of this method is
a List of Dictionaries, with each person represented by a single record
in the list.
Redistribution and use in source and binary forms, with or without modification
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, th
is list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Neither the name of the owner nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Read the current user's AddressBook database, converting each person
in the address book into a Dictionary of values. Some values (addresses,
phone numbers, email, etc) can have multiple values, in which case a
list of all of those values is stored. The result of this method is
a List of Dictionaries, with each person represented by a single recordq
in the list.
"""
# get the shared addressbook and the list of
# people from the book.
ab = ABAddressBook.sharedAddressBook()
people = ab.people()
peopleList = []
# convert the ABPerson to a hash
for person in people:
thisPerson = {}
props = person.allProperties()
for prop in props:
# skip some properties
if prop == "com.apple.ABPersonMeProperty":
continue
elif prop == "com.apple.ABImageData":
continue
# How we convert the value depends on the ObjC
# class used to represent it
val = person.valueForProperty_(prop)
if (type(val) == objc.pyobjc_unicode) and val != None:
if val.canBeConvertedToEncoding_(NSISOLatin1StringEncoding): #NSUTF8StringEncoding
thisPerson[prop.lower()] = val.cStringUsingEncoding_(NSISOLatin1StringEncoding)
else:
# Unicode String
thisPerson[prop.lower()] = '* '
thisPerson[prop.lower()] += val.cStringUsingEncoding_(NSUTF8StringEncoding)
# thisPerson[prop.lower()] = thisPerson[prop.lower()].encode('iso-8859-1', 'xmlcharrefreplace')
elif issubclass(val.__class__, NSDate):
# NSDate
thisPerson[prop.lower()] = val.description()
elif type(val) == ABMultiValueCoreDataWrapper:
# List -- convert each item in the list
# into the proper format
thisPerson[prop.lower()] = {} #[]
for valIndex in range(0, val.count()):
indexedValue = val.valueAtIndex_(valIndex)
indexedKey = val.labelAtIndex_(valIndex)
if indexedKey == val.primaryIdentifier:
indexedKey += '+'
if type(indexedValue) == objc.pyobjc_unicode:
# Unicode string
thisPerson[prop.lower()].update({indexedKey.cStringUsingEncoding_(4): indexedValue.cStringUsingEncoding_(4)})
elif issubclass(indexedValue.__class__, NSDate):
# Date
thisPerson[prop.lower()].update({prop.lower(): indexedValue.description()})
elif type(indexedValue) == NSCFDictionary:
# NSDictionary -- convert to a Python Dictionary
propDict = {}
for propKey in indexedValue.keys():
propValue = indexedValue[propKey]
propDict[propKey.lower()] = propValue
thisPerson[prop.lower()].update(propDict)
peopleList.append(thisPerson)
return peopleList
format = re.compile('[ -()-]')
m49 = re.compile('\+49')
mlist = addressBookToList()
#print kABPhoneHomeLabel
#print kABPhoneMobileLabel
#print kABPhoneWorkLabel
#print kABOtherLabel
#print mlist[25]
"""
<phonebooks>
<phonebook>
<contact>
<category>0</category>
<person><realName></realName></person>
<telephony>
<number type="home" vanity="" prio="1" quickdial="4"></number>
<number type="mobile" vanity="" prio="0"></number>
<number type="work" vanity="" prio="0"></number>
</telephony>
<services />
<setup />
</contact>
"""
# Create the minidom document
doc = Document()
phonebooks = doc.createElement("phonebooks")
doc.appendChild(phonebooks)
phonebook = doc.createElement("phonebook")
phonebooks.appendChild(phonebook)
for pers in mlist:
name = pers.get(kABLastNameProperty.lower(),'') + ', ' + pers.get(kABFirstNameProperty.lower(),'')
if name == ', ':
name = pers.get(kABOrganizationProperty.lower())
if name == None:
name = pers.get(kABNicknameProperty.lower())
# print name
if name == None:
name = "NoName?"
contact = doc.createElement("contact")
phonebook.appendChild(contact)
category = doc.createElement("category")
contact.appendChild(category)
# person with nik is important >:
nikname = pers.get(kABNicknameProperty.lower())
if nikname == None:
cat = "0"
else:
cat = "1"
cat_0 = doc.createTextNode(cat)
category.appendChild(cat_0)
person = doc.createElement("person")
contact.appendChild(person)
realname = doc.createElement("realName")
person.appendChild(realname)
p_inhalt = doc.createTextNode(name)
realname.appendChild(p_inhalt)
pers_phone = pers.get(kABPhoneProperty.lower())
if pers_phone != None:
telephony = doc.createElement("telephony")
contact.appendChild(telephony)
# quickdial = format.sub('' ,pers_phone.get( "Quickdial", '' ), 0)
prio = 1
home = format.sub('' ,pers_phone.get(kABPhoneHomeLabel,''), 0)
home = m49.sub('0', home)
if home != '':
number = None
number = doc.createElement("number")
telephony.appendChild(number)
number.setAttribute("type", "home")
number.setAttribute("vanity", "")
number.setAttribute("prio", '%d' % prio)
prio += 1
#number.setAttribute("quickdial", "")
num_home = doc.createTextNode(home)
number.appendChild(num_home)
mobile = format.sub('', pers_phone.get( kABPhoneMobileLabel, '' ), 0)
mobile = m49.sub('0', mobile )
if mobile != '':
number = None
number = doc.createElement("number")
telephony.appendChild(number)
number.setAttribute("type", "mobile")
number.setAttribute("vanity", "")
number.setAttribute("prio", '%d' % prio )
prio += 1
#number.setAttribute("quickdial", "")
num_home = doc.createTextNode(mobile)
number.appendChild(num_home)
work = format.sub('' ,pers_phone.get( kABPhoneWorkLabel, ''), 0)
work = m49.sub('0', work )
if work != '':
number = None
number = doc.createElement("number")
telephony.appendChild(number)
number.setAttribute("type", "work")
number.setAttribute("vanity", "")
number.setAttribute("prio", '%d' % prio)
prio += 1
#number.setAttribute("quickdial", "")
num_home = doc.createTextNode(work)
number.appendChild(num_home)
if ((home == '') and (mobile == '') and ( work == '')):
other = format.sub('' ,pers_phone.get( kABOtherLabel, '' ), 0)
other = m49.sub('0', other )
number = doc.createElement("number")
telephony.appendChild(number )
number.setAttribute("type", "home")
number.setAttribute("vanity", "")
number.setAttribute("prio", '%d' % prio)
prio += 1
#number.setAttribute("quickdial", "")
num_home = doc.createTextNode(other)
number.appendChild(num_home)
f = open('myfritz.adressbuch.xml', 'w')
doc.writexml(f, '', '', '' , 'iso-8859-1')
Hi!
Habe folgendes Problem.
Benutze die aktuelle Snow Leopard Version und erhalte folgende Fehlermeldung.
Irgendeine Idee was hier falsch laeuft?
Vielen Dank im Voraus.
Gruss,
Lars
Traceback (most recent call last):
File "iPyFritz.py", line 125, in
p_inhalt = doc.createTextNode(name)
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/xml/dom/minidom.py", line 1611, in createTextNode
raise TypeError, "node contents must be a string"
TypeError: node contents must be a string
Hi,
ja, ich denke ich ahne woran es liegt:
Ich versuche den Nach- und Vornamen zu ermitteln und mit Komma zusammenzusetzen:
if name == ', ':
name = pers.get(kABOrganizationProperty.lower())
Wenn das nicht klappt versuche ich es mit dem Organisations-Namen also Firmen-Namen.
Das heisst Du hast da vermutlich einen Eintrag der keinen Vor- oder Nachnamen enthält. Oder vielleicht hast du auch einen Spitznamen verwendet ohne Vor und Nachnamen – jedenfalls ist die Variable “name” leer und deswegen sagt er das es kein String ist.
Auf die Entfernung würde ich darum bitten ein paar “print” Befehle abzusetzen um den verqueren Adressbuch-Eintrag zu identifizieren. Im zweifel hilft ein
if name != None:
Joe
Ok, das mit Nick hat exakt diesen Fehler produziert – Ich habe das ZIP File aktualisiert – versuch noch mal mit der neuen Version… Viel Spass
Hallo und vielen Dank für das tolle Skript.
Ich kann leider gar kein Python. Aber äre es noch möglich die “wichtig” Variable zu berücksichtigen? Das ist meine Hauptanwendung für das Fritztelefonbuch, dass Leute die als “wichtig” markiert sind anders klingeln und eben durch die Klingelsperre kommen.
Z. B. könnte man aus dem Adressbuch das Feld “Friend” (kABFriendLabel) verwenden. Wenn dort nichts ist, dann 0, wenn dort 1 steht, dann 1.
Die Krönung wäre noch, wenn sich das Skript ohne Interaktion auf die Fritzbox laden könnte.
Danke und Gruß,
Marco
Irgendwie sind die Sachen in spitzen Klammern verloren gegangen.
ich meinte wenn a “Friend” (kABFriendLabel) leer, dann (category)0(/category), wenn 1, dann (category)1(/category).
Anstelle runder Klammern bitte spitze denken
Hi Mazo,
Danke das dir mein Skript gefällt – ich nehms mal als Anregung auf – und vielleicht habe ich demnächst mal Zeit.
Joe
So – Wenn ein Nickname gepflegt ist bekommt der Eintrag das “Wichtig”-Flag. Was ich noch ganz nett fände wäre es Schnellwahl-Einträge zu pflegen – das wir jedoch schwierig.
Zu der automatisierten übertragung an die Fritz.box – grundsätzlich kein Problem – nur muss man dann trotzdem irgendwo das Passwort hinterlegen.
Hi, ich habe das Problem dass polnische Sonderzeichen nicht richtig codiert werden. Statt einem Ł oder ą oder ś erscheint immer ein Å wenn ich die Datei importiere. Kann man was im script an den codiereinstellungen ändern und wenn ja, was? mir würde schon reichen wenn er aus einem Ł ein L und aus einem ą ein a macht. Danke & Gruß
thomas
Hi Thomas,
Deutsch ist mit ISO-8859-1 kodiert – so wie es aussieht ist Polnisch mit ISO-8859-2 kodiert (http://alis.isoc.org/codage/iso8859/jeuxiso.de.htm).
Versuch mal in der letzten Zeile des Scripts oben statt:
->
doc.writexml(f, '', '', '' , 'iso-8859-2')
Das Problem könnte aber noch an 1-2 anderen Stellen aufschlagen – zB. in der Fritzbox die bei mir im Export das ‘iso-8859-1′ explizit verlangt. MacosX und Python arbeiten beide mit UTF8 insofern könnte es klappen – poste mal das ergebnis…
Achso – wenn du mit Polnisch kodierst fliegen dir natürlich die Deutschen umlaute äüö raus – Ist leider nicht besser zu lösen – schöner wäre es, wenn die Fritzbox mit UTF8 umgehen könnte.
nope, da kommt der gleiche kram bei raus. die schwierigkeit liegt eher im herauskonvertieren der zeichen aus dem adressbuch. welche kodierung müsste ich verwenden damit python die sonderzeichen “glättet” dh aus einem ä ein a und aus einem Ä ein a macht?
Hi thomas,
ich sehe gerade im Coding sind noch einige Konvertierungen nach Ansi-ISO.
zB.
das wäre auch rauszunehmen.
Grundsätzlich ist das Problem leider nicht so einfach – am einfachsten wäre es wenn du im Adressbuch die Konvertierung schon vorgenommen hättest – sozusagen in der Quelle. Oder alternativ können wir gern auch einen Request bei AVM starten mit der bitte um UTF8-Unterstützung.
ich habe die NSISOLatin2StringEncoding statt der Latin1 gesetzt. Zumindest klappt damit die konvertierung im script. das problem ist später das einspielen in die box die nur iso-8859-1 beherrscht.
Moin,
gibt es eine Möglichkeit eine Untermenge des Adressbuchs über das Script zu exportieren? Beispielsweise eine bestimmte Adressbuch-Gruppe? Ich habe über 1000 Einträge im OS X Adressbuch und nicht alle brauche ich in der FritzBox, resp. könnte mehrere Submengen in verschiedene Telefonbücher packen (Verwandtschaft, Verein, Firma, …). Über launchd, resp. einem Helferlein namens Lingon habe ich auf apfeltalk.de einen Artikel veröffentlicht: http://www.apfeltalk.de/forum/lingon-gui-f-t98508.html Fragen können wir aber auch gerne bilateral klären.
Gruß Stefan
Hi Stefan,
Grundsätzlich “Ja” – Ich gehe davon aus das in irgendeinem Attribut die Gruppe gespeichert ist. Dadurch sollte es möglich sein nur spezifische Gruppen zu exportieren.
Mein Adressbuch ist nicht so groß – aber sobald ich Zeit finde schaue ich mal was ich rausfinde.
Joe
Vielen Dank für diese Script. Spart mir ne menge Arbeit und funktioniert perfekt!!
hilft das beim identifizieren der Gruppen-Zuordnung:
http://developer.apple.com/mac/library/documentation/userexperience/conceptual/AddressBook/AddressBook.html ?
Hallo Stefan,
…
soooo das hat jetzt zwei Fussballspiele zeit gekostet
Du musst die Zeile
entweder durch Variante 1:
groups = ab.groups()
for group in groups:
print group.valueForProperty_(kABUIDProperty),":", group.valueForProperty_(kABGroupNameProperty)
###
oder durch Variante 2:
suchstring = "MeineGruppe" # Es wird nach einem Substring gesucht
grsuche = ABGroup.searchElementForProperty_label_key_value_comparison_(kABGroupNameProperty, None , None , suchstring, kABContainsSubStringCaseInsensitive)
print grsuche
groups = ab.recordsMatchingSearchElement_(grsuche)
for group in groups:
print group.valueForProperty_(kABUIDProperty),":", group.valueForProperty_(kABGroupNameProperty)
#####
ersetzen und anschliessend folgende Zeile ergänzen:
Hallo,
VIELEN DANK!
Das ist ein fantastisches Script. Tut genau was es soll! Perfekt!!
Beim Import ist mir aufgefallen, dass auch Einträge ohne Telefonnummern erstellt werden. In meinem Adressbuch gibt es solche Einträge, also funktioniert das Script wie erwartet – auch wenn das in diesem Fall keinen Sinn macht.
Wäre es aufwändig, eine Prüfung einzubauen die verhindert, dass Einträge ohne Telefonnummer überhaupt erstellt werden?
Nachdem ich das “Spitzname”-Feld häufiger verwende, auch für Leute die eher ins Archiv gehören, würde ich wie mazo die Verwendung des “Friend”-Felds vorschlagen. Die Änderung am Python-Code ist ja trivial. Allerdings … finde ich das Feld “Friend” oder “Freund” nicht im Adressbuch…
Auch wenns peinlich ist, aber welches Feld entspricht denn im Deutschen “kABFriendLabel”?
Gedanken zu einem automatischen Sync:
a) Man kann auf dem Mac per launchd automatisch einen Adressbuch-Export zu bestimmten Zeiten erstellen.
b) Die resultierende Adressdatei könnte dann
Weg 1) per SMB auf die Fritz!Box (USB-Stick/-Platte)
oder
Weg 2) per FTP/WebDAV auf eine Freigabe im Internet
hinterlegt werden.
c) Auf der Fritz!Box wird periodisch per cron-Script die Adressdatei
Weg 1) ins entsprechende Unterverzeichnis verschoben
Weg 2) heruntergeladen und ins entsprechende Unterverzeichnis verschoben
Das würde voraussetzen, dass die Datei, die vom Python-Script erzeugt wird, schon dem Format entspricht, das beim Import auf der Fritz!Box generiert wird.
Geht das?
Oder gibt es shell-Befehle um das Adressbuch automatisch auf der Fritz!Box zu importieren?
> …würde ich wie mazo die Verwendung des “Friend”-Felds vorschlagen.
hätte heissen sollen:
> …würde ich selbst wie mazo es vorschlägt das “Friend”-Feld nutzen wollen.
Ich meine also: Ich würde das Script für meine Fälle anpassen. Sicherlich ist das Spitzname-Feld für die meisten Anwender die bessere Wahl.
Der automatische Export auf dem Mac kann man mit einem launchd-Script realisieren.
Den Import auf der FRITZ!Box mit pbd, noch nicht automatisch, da der FRITZ!Box ein cron-like Automatismus fehlt, aber auch das lässt sich auf Umwege lösen.
Eine kleine (!) Anleitung habe ich hier hinterlegt: http://know-how.posterous.com/aktualisierung-von-fritzbox-adressbuch-mit-os
Und die Scripte dazu hier: http://know-how.posterous.com/fritzbox-adressbuch-scripts
Hi Wolf,
sehr cool – werd ich mal auftesten.
Hi Joe,
der automatische Import ist recht simpel, wenn auf der FRITZ!Box ein SSH-Zugang existiert.
Dann kann vom Script am Mac aus
- der Export des Adressbuchs gemacht,
- das Verschieben auf die FRITZ!Box vorgenommen, und
- der Import per SSH-Befehl angestossen werden.
Die nächste Version ist in Arbeit und kann zum testen hier:
http://www.web-dreamer.de/blog/2010/08/mac-adressbuch-fur-avm-fritzbox-version-2-os-x-ipyfritz.html
gefunden werden…