abook Adressen importieren

abook Addressbuch unter Linux

Was ist das doch manchmal schwierig Informationen zu finden. Zum Glück kann man ja heute mithilfe von so LLMs, auch in den Gebieten fischen, in denen man sich nicht so gut auskennt. Deshalb lasse ich Euch hier mal teilhaben.

Ausgangspunkt

Ich habe mir alle Kontakte aus meinem Google Konto, von Apple Kontakte, in eine vcf-Datei exportieren lassen. Diese Daten würde ich nun gern in abook importieren, damit ich die Adressen z.B. in neomutt weiterverwenden kann.

Hier einmal der Code komplett mit Anmerkungen, welche Funktion, was macht.

Script für die Konvertierung >folded
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/env python

import sys # Importiert das sys-Modul für die Arbeit mit Systemparametern
import csv # Importiert das csv-Modul für die Arbeit mit CSV-Dateien
from vobject import readComponents # Importiert die Funktion readComponents aus dem vobject-Modul zur Verarbeitung von vCard-Daten

# Definieren Sie den Pfad zur vcf-Datei und die Ausgabe-Addressbook-Datei
vcf_file = '~/Downloads/vcard-export/alle-kontakte.vcf' # Pfad zur vcf-Datei
addressbook_file = '~/Downloads/vcard-export/addressbook.abook' # Pfad zur Ausgabe-Datei im abook-Format

try:
with open(vcf_file, 'r') as f: # Öffnet die vcf-Datei im Lesemodus
vcards = list(readComponents(f)) # Liest und parst die vCard-Daten aus der Datei
except Exception as e: # Fehlerbehandlung bei Dateizugriffsfehlern
print(f"Error reading {vcf_file}: {e}") # Gibt eine Fehlermeldung aus, falls ein Fehler auftritt
sys.exit(1) # Beendet das Programm mit einem Fehlercode

# Funktion zur Extraktion des ersten Werts eines Attributs aus einem vCard-Objekt
def get_first_value(obj, attribute):
if hasattr(obj, attribute): # Prüft, ob das Attribut existiert
attr = getattr(obj, attribute) # Holt das Attribut
if isinstance(attr, list): # Prüft, ob das Attribut eine Liste ist
return attr[0].value # Gibt den ersten Wert der Liste zurück
return attr.value # Gibt den Wert des Attributs zurück
return 'N/A' # Gibt 'N/A' zurück, falls das Attribut nicht existiert

# Funktion zur Extraktion aller Werte eines Attributs aus einem vCard-Objekt
def get_all_values(obj, attribute):
if hasattr(obj, attribute): # Prüft, ob das Attribut existiert
attr = getattr(obj, attribute) # Holt das Attribut
if isinstance(attr, list): # Prüft, ob das Attribut eine Liste ist
return ', '.join([a.value for a in attr]) # Gibt alle Werte als kommaseparierte Zeichenkette zurück
return attr.value # Gibt den Wert des Attributs zurück
return 'N/A' # Gibt 'N/A' zurück, falls das Attribut nicht existiert

# Funktion zur Extraktion einer spezifischen Adresskomponente aus einem adr-Objekt
def get_address_component(adr, component):
return getattr(adr, component, '') # Gibt die spezifische Adresskomponente zurück oder ein leeres Zeichen, falls sie nicht existiert

# Funktion zur Extraktion der Adressinformationen aus einem vCard-Objekt
def get_address(vcard):
if hasattr(vcard, 'adr'): # Prüft, ob das adr-Attribut existiert
adr = vcard.adr.value # Holt das adr-Attribut
address = get_address_component(adr, 'street') # Extrahiert die Straßenadresse
address2 = '' # Setzt address2 auf leer, da es nicht in vCard enthalten ist
city = get_address_component(adr, 'city') # Extrahiert die Stadt
state = get_address_component(adr, 'region') # Extrahiert den Staat/Region
zip_code = get_address_component(adr, 'code') # Extrahiert die Postleitzahl
country = get_address_component(adr, 'country') # Extrahiert das Land
return address, address2, city, state, zip_code, country # Gibt die Adresskomponenten zurück
return 'N/A', 'N/A', 'N/A', 'N/A', 'N/A', 'N/A' # Gibt 'N/A' zurück, falls die Adresse nicht existiert

# Öffnet die abook-Datei im Schreibmodus
with open(addressbook_file, 'w') as abookfile:
# Schreibt die Kopfzeilen der abook-Datei
abookfile.write('[format]\n')
abookfile.write('program=abook\n')
abookfile.write('version=0.6.1\n\n')

# Iteriert über die vCards und schreibt ihre Daten in die abook-Datei
for i, vcard in enumerate(vcards):
name = get_first_value(vcard, 'fn') # Holt den Namen
email = get_all_values(vcard, 'email') # Holt alle E-Mail-Adressen
address, address2, city, state, zip_code, country = get_address(vcard) # Holt die Adressinformationen
phone = get_all_values(vcard, 'tel') # Holt alle Telefonnummern
workphone = get_first_value(vcard, 'workphone') # Holt die geschäftliche Telefonnummer
fax = get_first_value(vcard, 'fax') # Holt die Faxnummer
mobile = get_first_value(vcard, 'cell') # Holt die Handynummer
nickname = get_first_value(vcard, 'nickname') # Holt den Spitznamen
url = get_first_value(vcard, 'url') # Holt die URL
notes = get_first_value(vcard, 'note') # Holt die Notizen
anniversary = get_first_value(vcard, 'bday') # BDAY wird als anniversary gespeichert
groups = get_all_values(vcard, 'categories') # Holt alle Gruppen

# Schreibt die vCard-Daten in das abook-Format
abookfile.write(f'[{i}]\n')
abookfile.write(f'name={name}\n')
abookfile.write(f'email={email}\n')
if address != 'N/A':
abookfile.write(f'address={address}\n')
if address2 != 'N/A':
abookfile.write(f'address2={address2}\n')
if city != 'N/A':
abookfile.write(f'city={city}\n')
if state != 'N/A':
abookfile.write(f'state={state}\n')
if zip_code != 'N/A':
abookfile.write(f'zip={zip_code}\n')
if country != 'N/A':
abookfile.write(f'country={country}\n')
if phone != 'N/A':
abookfile.write(f'phone={phone}\n')
if workphone != 'N/A':
abookfile.write(f'workphone={workphone}\n')
if fax != 'N/A':
abookfile.write(f'fax={fax}\n')
if mobile != 'N/A':
abookfile.write(f'mobile={mobile}\n')
if nickname != 'N/A':
abookfile.write(f'nick={nickname}\n')
if url != 'N/A':
abookfile.write(f'url={url}\n')
if notes != 'N/A':
abookfile.write(f'notes={notes}\n')
if anniversary != 'N/A':
abookfile.write(f'anniversary={anniversary}\n')
if groups != 'N/A':
abookfile.write(f'groups={groups}\n')
abookfile.write('\n') # Fügt eine Leerzeile nach jedem Eintrag hinzu

print(f"Successfully written data to {addressbook_file}") # Gibt eine Erfolgsnachricht aus

# Ende des Skripts

Das Ergebnis sieht dann wie folgt aus. So kann man mit der Datei weiterarbeiten. Das Format ist gleich korrekt und wird von abook ohne Murren verarbeitet. Ich hatte mehrere Versuche und bei den ersten wurde die Datei direkt gelöscht. Das lag offensichtlich daran, dass ich Probleme mit dem Format hatte.

1
2
3
4
5
6
7
8
9
10
11
12
# abook addressbook file

[format]
program=abook
version=0.6.1


[0]
name=Autohaus Ludwigshaven
email=N/A
address=N/A
phone=+490026950000

In einer der ersten Versionen wurden die Geburtstage nicht korrekt übernommen. Die habe ich in einer der Iterationen dann von BDAY in das Feld Anniversary übergeben. So passte das für mich besser.

tmux ist mein Tiling Window Manager

TMUX

Ich hatte da mal einen Post auf reddit geesehen und mir gedacht, das klingt soch irgendwie nach einem Plan. Mit dem neuen Rechner den ich bekommen habe war das eine gute Gelegenheit. Hier kann ich in einem Terminal Fenster alle Prozesse offen halten, die ich so brauche, oder an denen ich schon mal Spass habe.

Timewarrior / Taskwarrior

Die beiden Programme verwende ich um per CLI mal eben schnell einen Task zu erstellen, zu starten oder zu beenden. In der Kombination mit Timewarrior habe ich dann auch noch ein Tool, dass die Zeiten speichert, die ich für einzelne Tasks oder die verschiedenen Projekte aufgewndet habe.

wttr.in

Man muss doch immer das Wetter ein bisschen im Blick habe.

wttr.in

NCSPOT

Spotify in der CLI? Ja klar, hast Du schon mal geschaut wie viel Speicher Spotify sich reserviert? Ich habe aktuell nur 32 Gig, da muss ich ein wenig haushalten.

Spotify in der CLI mit NCSPOT

Spotify Speicherfresser

Ich hatte hier gestern erst 70 MB Speicherverbrauch, die 106 heute erscheinen mir ein bisschen viel.

Nachtrag

Wusste ich doch, das der Speicherverbrauch eigentlich total niedrig ausfällt:

ncspot mit 28MB Speicherverbrauch

ncspot Meeresrauschen

Newsboat RSS Reader

Noch bin ich nicht dazu gekommen mir emacs einmal genauer anzuschauen, außerdem will ich nicht schon wieder ein Fass ohne Boden aufmachen.

Newsboat RSS Reader

Was ich an newsboat so mag ist, dass ich hier mit tmux, in 3 Fenstern ein wenig sortiert meine wichtigsten RSS-Feeds im Blick halten kann. Ich habe 3 verschiedene Config-Dateien hinterlegt und kann so meine RSS-Feeds, meine youtube-Feeds und meine wichtigsten Podcasts im Blickbehalten. Mit mpv kann ich Mediendateien abspielen, also Podcasts oder Videos und überall wo das nicht funktioniert, wird per URL-View einfach der Standard Browser vom System aufgerufen.

Nur falls jemand ein bisschen was abschauen will:

#  _   _ ________          _______ ____   ____       _______
# | \ | |  ____\ \        / / ____|  _ \ / __ \   /\|__   __|
# |  \| | |__   \ \  /\  / / (___ | |_) | |  | | /  \  | |
# | . ` |  __|   \ \/  \/ / \___ \|  _ <| |  | |/ /\ \ | |
# | |\  | |____   \  /\  /  ____) | |_) | |__| / ____ \| |
# |_| \_|______|   \/  \/  |_____/|____/ \____/_/    \_\_|
#
#
browser $BROWSER
save-path "~/Dokumente/rss-feeds-worth-saving/"
download-path ~/Musik
player "mpv"
#
# add video or audio to play queue using mpv (requires: task-spooler, mpv, youtube-dl)
# task-spooler: https://www.youtube.com/watch?v=wv8D8wT20ZY
# youtube-dl: https://www.youtube.com/watch?v=MFxlwVhwayg
 macro p set browser "mpv --ontop --no-border --force-window --autofit=500x280 --geometry=-15-10 %u"; open-in-browser ; set browser "$BROWSER %u"
 refresh-on-startup yes
 auto-reload yes
 reload-time 30
 reload-threads 100
 external-url-viewer "urlview"
 # external-url-viewer "urlscan"
 bind-key U show-urls
 # max-items        100
 confirm-delete-all-articles yes
 scrolloff 25
 # notify-xterm yes
 suppress-first-reload yes
 display-article-progress yes
 confirm-mark-all-feeds-read yes
 confirm-mark-feed-read no
 # error-log ~/.local/share/newsboat/error.log
 #https://gitlab.com/dj-bauer/newsboat-rice/-/blob/master/config
 highlight feedlist "[╒╘╞]═.*═[╛╕╡]" color175 color237  #yellow default bold
 highlight feedlist "[║│]" color175 color237 bold #yellow default bold
 highlight feedlist "╠═.*" color175 color237 bold  #yellow default bold
 # highlight feedlist "\\(youtube\\) .*" red
 # highlight feedlist "\\(Reddit\\) .*" green
 # highlight feedlist "\\(Podcast\\) .*" magenta
 # highlight feedlist "\\(Blogs\\) .*" cyan
 # highlight feedlist "\\(Reddit\\) .*" color166
 feedlist-format "%?T?║%4i %n %14u (%T) %S &╠════════════════════════════════════════════════════%t?"
 feedlist-title-format "%N %V - Deine Feeds ║ (%u ungelesen, %t Gesamt)%?T? - tag ’%T'&? "
 # keybindings because of muscle memory.
 bind-key SPACE next-unread
 bind-key j down
 bind-key k up
 bind-key g home
 bind-key G end
 bind-key RIGHT open
 bind-key U show-urls

Noch ein Post über newsboat

Newsboat RSS Reader

Zur Erinnerung hier noch mal ein Auszug aus dem letzten Artikel:

RSS Reader mit Podcast unterstützung.

Was der super macht, ist Artikel herunterladen und in einem Ordner meiner Wahl speichern für spätere Referenzierung. Außerdem kann ich meine Lieblingsradiosendung herunterladen, WeFunkRadio. So habe ich die neuen Folgen jederzeit verfügbar auf der Festplatte liegen. Mit ein bisschen Anpassungen in der Konfig-Datei kann man sich einen netten Überblick verschaffen, was heute so wichtig war.

Übersicht über die ungelesenen Feeds

In der URL Datei kann man mit RegEx ein bisschen was tricksen und für Übersicht sorgen

Als textbasierten Browser verwende ich lynx. Aber mit ,s kann ich bei Bedarf surf von Suckless starten, mit ,f Firefox und mit ,p werden Mediendateien wiedergegen, wie z.B. youtube Videos oder Podcasts. Wie ich finde eine gelungene Sache. Jede Woche entdecke ich was neues und komme immer besser zurecht.

Warum gibt es einen zweiten Teil?

Seit dem ersten Artikel habe ich noch ein bisschen mit der Konfguration herumgespielt und noch ein paar youtube Videos zu dem Thema gesehen. Auf das lesen der Manpage hatte ich bisher noch keine Lust, das meiste bekommt man ja auch so raus.

In der URL Datei kann man mit RegEx ein bisschen was tricksen und für Übersicht sorgen

Das sieht am Ende dann doch alles ein bisschen netter aus und sorgt für mehr Übersicht.

So sieht das ganze dann aus

Am Ende jeder Zeile sind Indikatoren (x) mit denen man erkennen kann, dass aktuell noch geladen wird.

In der ersten Zeile der URL-Datei habe ich noch die Zeile:

"query:Markiert:flags # \"s\"" "Markierte-Artikel"

eingefügt. Hier kann ich nun in den Artikeln mit STRG + F für "Flags" mit einem einfachen s und ENTER, einen Artikel für später markieren.

newsboat übersicht mit laufendem Podcast im mpv

Aktuell bekomme ich das mit den Farben noch nicht so geregelt wie ich mir da vorstelle, da brauche ich noch ein bisschen Zeit.