#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ contacts_to_remind.py - Exportiert Geburtstage aus Apple Contacts in eine Remind-Datei Dieses Script exportiert Geburtstage aus Apple Contacts und erstellt eine Remind-Datei (geburtstage.rem) für Geburtstagserinnerungen. Creation Date: 2025-04-18 Revision: 1.3 """ import os import sys import datetime import argparse from pathlib import Path try: # Verwenden von pyobjc-framework-Contacts für den Zugriff auf Apple Contacts from Contacts import CNContactStore, CNContactFetchRequest, CNContactBirthdayKey, CNContactGivenNameKey, CNContactFamilyNameKey import objc except ImportError: print("Die 'pyobjc-framework-Contacts' Bibliothek wird benötigt. Bitte installieren Sie sie mit:") print("pip install pyobjc-framework-Contacts") print("oder") print("uv pip install pyobjc-framework-Contacts") sys.exit(1) def get_birthdays_from_contacts(): """Ruft alle Kontakte mit Geburtstagsinformationen aus Apple Contacts ab.""" store = CNContactStore.alloc().init() # Anfrage für Kontakte mit Geburtstagsinformationen erstellen keys_to_fetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactBirthdayKey] request = CNContactFetchRequest.alloc().initWithKeysToFetch_(keys_to_fetch) birthdays = [] placeholder_contacts = [] # Kontakte mit dem Platzhalter-Jahr 1604 # Callback-Funktion für jeden gefundenen Kontakt def handle_contact(contact, stop): birthday = contact.birthday() if birthday: given_name = contact.givenName() or "" family_name = contact.familyName() or "" name = f"{given_name} {family_name}".strip() # Sicherstellen, dass wir tatsächlich Integer-Werte haben try: month = birthday.month() day = birthday.day() # Prüfen, ob ein Jahr existiert try: year = birthday.year() # In Apple Contacts ist 1604 oft ein Platzhalter für "kein Jahr" if year == 1604: placeholder_contacts.append({ 'name': name, 'month': month, 'day': day }) year = None except: year = None # Nur hinzufügen, wenn gültige Monat- und Tagwerte vorhanden sind if isinstance(month, int) and isinstance(day, int): birthdays.append({ 'name': name, 'month': month, 'day': day, 'year': year if isinstance(year, int) and year != 1604 else None }) except: # Fehler beim Extrahieren der Datumsinformationen pass # Anfrage ausführen mit der Callback-Funktion error = objc.nil store.enumerateContactsWithFetchRequest_error_usingBlock_(request, error, handle_contact) return birthdays, placeholder_contacts def create_remind_file(birthdays, output_file='geburtstage.rem'): """Erstellt eine Remind-Datei mit den Geburtstagsinformationen.""" current_year = datetime.datetime.now().year with open(output_file, 'w', encoding='utf-8') as f: f.write("# Geburtstage aus Apple Contacts\n") f.write("# Erstellt am: {}\n".format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) f.write("# Enthält Erinnerungen am Geburtstag mit 7-Tage-Vorwarnung\n\n") # Add the since() function definition f.write("if !defined(\"init\")\n") f.write(" set init 1\n") f.write(" fset since(x) ord(year(trigdate())-x)\n") f.write("endif\n\n") for person in sorted(birthdays, key=lambda x: (x['month'], x['day'])): name = person['name'] month = person['month'] day = person['day'] birth_year = person['year'] # Verwende since() Funktion, wenn Geburtsjahr verfügbar ist if birth_year and isinstance(birth_year, int) and birth_year > 1900 and birth_year < current_year: # Erinnerung mit 7-Tage-Vorwarnung remind_entry = f'REM {day} {month_to_abbr(month)} +7 MSG {name}s [since({birth_year})] Geburtstag ist am {day}. {month_to_abbr(month)} %b\n' f.write(remind_entry) else: # Kontakte ohne Geburtsjahr, Erinnerung mit 7-Tage-Vorwarnung remind_entry = f'REM {day} {month_to_abbr(month)} +7 MSG {name}s Geburtstag ist am {day}. {month_to_abbr(month)} %b\n' f.write(remind_entry) def month_to_abbr(month_num): """Konvertiert eine Monatsnummer (1-12) in die dreistellige Abkürzung für Remind.""" months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] return months[month_num - 1] def print_placeholder_report(placeholder_contacts): """Gibt einen Bericht über Kontakte mit dem Platzhalter-Jahr 1604 aus.""" if not placeholder_contacts: print("\nKeine Kontakte mit fehlendem Geburtsjahr gefunden.") return print("\nKontakte mit fehlendem Geburtsjahr (1604):") print("-----------------------------------------") for person in sorted(placeholder_contacts, key=lambda x: x['name']): print(f"• {person['name']} ({person['day']}.{person['month']}.)") print("\nHinweis: Bei diesen Kontakten wird kein Alter berechnet.") def main(): """Hauptfunktion des Scripts.""" parser = argparse.ArgumentParser(description='Exportiert Geburtstage aus Apple Contacts in eine Remind-Datei.') parser.add_argument('-o', '--output', default='geburtstage.rem', help='Ausgabedatei (Standard: geburtstage.rem)') parser.add_argument('-v', '--verbose', action='store_true', help='Ausführliche Ausgabe') args = parser.parse_args() try: if args.verbose: print("Lese Kontakte aus Apple Contacts...") birthdays, placeholder_contacts = get_birthdays_from_contacts() if args.verbose: print(f"{len(birthdays)} Kontakte mit Geburtstagsinformationen gefunden.") create_remind_file(birthdays, args.output) print(f"Remind-Datei erfolgreich erstellt: {args.output}") print(f"Insgesamt {len(birthdays)} Geburtstage exportiert.") # Bericht über Kontakte mit fehlendem Geburtsjahr ausgeben print_placeholder_report(placeholder_contacts) except Exception as e: print(f"Fehler: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()