// Variables used by Scriptable. // These must be at the very top of the file. Do not edit. // icon-color: deep-gray; icon-glyph: birthday-cake; ////////////////////////////////// // Adaptation: Feb, 2023 -> https://anb030.de // Color of S-Bahn (Berlin): Ockergelb #C0A754 & Rubinrot #861A22 // Original source of code: Jan,2021 -> https://github.com/lwitzani/daysUntilBirthday // ////////////////////////////////// // Verwendung: // Modus 1 (Standard): Nur ausgewählte Kontakte: // Bearbeiten Sie in Ihrer Kontakt-App die Kontakte, die in diesem Widget sichtbar sein sollen // -> Sie müssen ein zusätzliches 'Datums'-Feld in Ihrem Kontakt einrichten und dem Datum die Bezeichnung 'NextBirthdays' geben // Führen Sie das Skript zunächst in der Scriptable-App aus, um eine json-Datei in der iCloud zu erstellen, die Kontaktinformationen für einen schnelleren Zugriff enthält // wenn Sie neue Kontakte über das Label hinzufügen, führen Sie das Skript erneut in der App aus, um die json-Datei zu aktualisieren und die Änderungen im iCloud-Modus sichtbar zu machen // wenn Sie das Skript als Widget einrichten, verwenden Sie den größten Präsentationsmodus und geben Sie den Parameter 'iCloud' (ohne das '') an // // Modus 2: Alle Kontakte mit einem konfigurierten Geburtstag anzeigen // Setzen Sie die nächste Variable auf true oder geben Sie den Parameter 'showAll' im Widget-Modus an, um alle Kontakte anzuzeigen, die einen Geburtstag im regulären Geburtstagsfeld konfiguriert haben // let showAllContacts = true; // // iCloud-Mode: // Setzen Sie die nächste Variable auf true oder geben Sie den Parameter 'iCloud' im Widget-Modus an, damit nie wieder neu berechnet wird, welche Kontakte angezeigt werden // if false -> Jedes Mal, wenn die Kontakte gescannt werden // if true -> Kontakte werden nicht gescannt und zuletzt verwendete Kontakte werden wieder verwendet let useIcloud = false; ////////////////////////////////// // Die Bezeichnung, die Sie in den Kontakten für das erste Datumsfeld festlegen müssen const contactNotesKeyWord = 'NextBirthdays'; ////////////////////////////////// // Bearbeiten Sie diesen Absatz entsprechend in Ihrer Sprache const NextBirthdays = ' 🅝ⒺⓍⓉ 🅑ⒹⒶⓎⓈ'; const daysText = 'Tage'; const todayText = 'Heute'; ////////////////////////////////// const dateFormatter = new DateFormatter(); dateFormatter.dateFormat = 'dd.MM.yy'; const timeFormatter = new DateFormatter(); timeFormatter.dateFormat = 'dd.MM.yyyy HH:mm'; const headerFont = new Font('Bradley Hand', 24); const contactNameFont = new Font('Verdana Bold', 18); const smallInfoFont = new Font('Verdana Bold', 10); const updatedAtFont = new Font('Savoye LET', 8); const fontColorTwo = new Color("861a22"); const fontColorOne = new Color("#202020"); // Wird zum Einfügen von Leerzeichen verwendet const lineLength = 6; // Klasse, die auch in eine json-Datei in iCloud serialisiert wird class CustomContact { constructor(name, daysUntil, date) { this.name = name; this.daysUntil = daysUntil; this.date = date; } getAsKey() { // name and daysUntil together make the contact unique return this.name + '-' + this.daysUntil; } } const widget = await createWidget(); widget.backgroundColor = new Color("#C0A754"); if (!config.runsInWidget) { await widget.presentMedium(); } Script.setWidget(widget); Script.complete(); async function createWidget() { // Überschreiben Sie die Standardwerte oben, wenn Sie als Widget laufen // Beispiele für Arbeitsparameter: 'iCloud,showAll', 'showAll,iCloud', 'iCloud', 'showAll' if (args.widgetParameter) { if (args.widgetParameter.includes('iCloud')) { useIcloud = true; } if (args.widgetParameter.includes('showAll')) { showAllContacts = true; } } const widget = new ListWidget(); let headerRow = widget.addStack(); let headerText = headerRow.addText(NextBirthdays); headerText.textColor = fontColorTwo; headerText.font = headerFont; let shownCustomContacts = []; let dataSource = ''; // Geben Sie "iCloud" ohne das '' als Parameter ein, wenn Sie das Skript als Widget einrichten if (useIcloud) { dataSource = 'Heute: '; shownCustomContacts = loadCustomContacts(); updateCustomContacts(shownCustomContacts); } else { dataSource = ''; let containers = await ContactsContainer.all(); let contactsInIos = await Contact.all(containers); let keysForDuplicatePrevention = []; for (let contact of contactsInIos) { let dateToUse = null; // Wenn gesetzt, wird ein Kontakt gefunden if (showAllContacts) { // Modus 2: alle Kontakte anzeigen, bei denen ein reguläres Geburtstagsfeld gesetzt ist if (contact.isBirthdayAvailable) { dateToUse = contact.birthday; dateToUse = getFixedDate(dateToUse); } } else { // Modus 1: nur ausgewählte Kontakte anzeigen // Kontakte müssen eine zusätzliche Datumseigenschaft haben, die wie der Inhalt der Variable contactNotesKeyWord benannt ist if (contact.dates) { for (let date of contact.dates) { if (date.label.startsWith(contactNotesKeyWord)) { dateToUse = date.value; dateToUse = getFixedDate(dateToUse); } } } } if (!dateToUse) { // contact Sollte nicht angezeigt werden -> weiter zum nächsten Kontakt continue; } // Wenn hier: Kontakt wird angezeigt // Der kürzere Spitzname wird bevorzugt let contactsName = contact.nickname ? contact.nickname : contact.givenName; // In der nächsten Zeile werden Emoji entfernt, die nach einem Leerzeichen kommen contactsName = contactsName.split(' ')[0]; let foundContact = new CustomContact(contactsName, calculateDaysUntil(dateToUse), dateFormatter.string(new Date(dateToUse))); // Prüfen, ob bereits vorher gefunden (bei mehreren Kontaktcontainern) if (!keysForDuplicatePrevention.includes(foundContact.getAsKey())) { keysForDuplicatePrevention.push(foundContact.getAsKey()); shownCustomContacts.push(foundContact); } } } // Sortiert Kontakte danach, wie nah ihr Geburtstag ist shownCustomContacts.sort(function (a, b) { return a.daysUntil > b.daysUntil; }); // Zurückschreiben in json in iCloud saveCustomContacts(shownCustomContacts); // diese Zeile besteht aus zwei customContact-Informationen let currentRow; // Zähler für die Erstellung von zwei Spalten und maximal 20 sichtbaren Kontakten let contactCounter = 0; for (let customContact of shownCustomContacts) { if (contactCounter === 4) { // Nur die 20 frühesten Geburtstage werden im Widget angezeigt break; } if (contactCounter % 2 === 0) { // Beginn einer neuen Zeile currentRow = widget.addStack(); } addContactInfoToRow(customContact, currentRow); contactCounter++; if (contactCounter < 20) { widget.addSpacer(5); } } let updatedAt = widget.addText('' + dataSource + '' + timeFormatter.string(new Date())); updatedAt.font = updatedAtFont; updatedAt.textColor = fontColorOne; updatedAt.centerAlignText(); return widget; } // Zum Ausrichten der Informationen function addSpaces(amount, row) { for (let i = 0; i < amount; i++) { let text = row.addText(' '); text.font = contactNameFont; } } function addContactInfoToRow(customContact, row) { addSpaces(lineLength - customContact.name.length, row); let nameRow = row.addText(customContact.name); nameRow.font = contactNameFont; nameRow.textColor = fontColorOne; let actualText = customContact.daysUntil === 0 ? ' ' + todayText + '\n ' + customContact.date.replace('.2222', '.????') : ' ' + customContact.daysUntil + ' ' + daysText + '\n ' + customContact.date.replace('.2222', '.????'); let daysInfoText = row.addText(actualText); daysInfoText.textColor = fontColorTwo; daysInfoText.font = smallInfoFont; } function calculateDaysUntil(birthdayString) { let startDate = new Date(); let targetDate = new Date(birthdayString); targetDate.setFullYear(startDate.getFullYear()); let timeRemaining = parseInt((targetDate.getTime() - startDate.getTime()) / 1000); if (timeRemaining < 0) { // the date was in the past -> recalculate for next year targetDate.setFullYear(targetDate.getFullYear() + 1); timeRemaining = parseInt((targetDate.getTime() - startDate.getTime()) / 1000); } if (timeRemaining >= 0) { let days = 1 + parseInt(timeRemaining / 86400); return parseInt(days, 10) % 365; } else { return '???'; } } // Berechnet den daysUntil-Wert der customContacts neu function updateCustomContacts(customContacts) { for (let contact of customContacts) { let date = dateFormatter.date(contact.date); date = getFixedDate(date); contact.daysUntil = calculateDaysUntil(date.toString()); } } // Lädt die in der json-Datei gespeicherten Kontakte function loadCustomContacts() { // Dies könnte im FileManager.local() geändert werden, wenn Sie iCloud nicht verwenden möchten let fm = FileManager.iCloud(); let path = getFilePath(); if (fm.fileExists(path)) { let raw = fm.readString(path); return JSON.parse(raw); } else { return []; } } // Speichert die CustomContacts in einer Datei in iCloud Drive. function saveCustomContacts(customContacts) { // Dies könnte im FileManager.local() geändert werden, wenn Sie iCloud nicht verwenden möchten let fm = FileManager.iCloud(); let path = getFilePath(); let raw = JSON.stringify(customContacts); fm.writeString(path, raw); } // Ermittelt den Pfad der Datei, die die gespeicherten CustomContact-Daten enthält. Erstellt die Datei, falls erforderlich. function getFilePath() { let fm = FileManager.iCloud(); let dirPath = fm.joinPath(fm.documentsDirectory(), "NextBirthdays"); if (!fm.fileExists(dirPath)) { fm.createDirectory(dirPath); } return fm.joinPath(dirPath, "customContacts.json"); } function getFixedDate(date) { if (date?.getFullYear() === 1) { // Verrückter Fehler in ios-Kontakten, wenn kein Jahr eingestellt ist... date = new Date(0000, date.getMonth(), date.getDate()); date.setDate(date.getDate() + 2); } return date; }