Forum Archive

Contacts API - Group Modification

hendrikstier

Hello!

I'm currently trying to implement a small script to explicitly set/change a single contact's group. I'm not sure how to use the API.

Reading through http://omz-software.com/pythonista/docs/ios/contacts.html, I find ways to get all groups or those specific to a user, but I do not find information on how to set or change a user's group.

Did anyone already successfully implement this functionality?

Kind regards,

Hendrik

ccc

Python introspection to the rescue...

import contacts
print(dir(contacts.get_all_groups()[-1]))  # dir(the last Group in Contacts)

Group has three methods add_member(), get_members(), and remove_member().

hendrikstier

Thank you, this is exactly the pointer I need..

hendrikstier

A quick follow-up question on groups: I try to move a locally created contact to a group that is supposed to reside on a CardDAV-server.

Is there any way to distinguish between the individual accounts the groups belong to? I ask, because I can iterate over my iCloud-stored groups, but the CardDAV-stored group seems to be missing.

I will try and find my way in reverse (find a user, that is stored in the CardDAV-group and inspect the attributes), but maybe there is already some advice on how to speed up my learning here...

osamu

My Pythonista 3 does not have these methods😥

>>> import contacts
>>> p = contacts.find('foo')[0]
>>> g = contacts.Group()
>>> g.name = 'foobar '
>>> g = contacts.add_group(g)
>>> contacts.add_member(p,g)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: module 'contacts' has no attribute 'add_member'
>>> print(dir(contacts.get_all_groups())[-1])

sort

Where have they gone?

cvp

@osamu small errors

import contacts
p = contacts.find('foo')[0]
g = contacts.Group()
g.name = 'foobar '
contacts.add_group(g)
g.add_member(p)
contacts.save()

If after multiple tests, you get multiple groups named "foobar", you can remove them via

import contacts
for g in contacts.get_all_groups():
    print(g.name)
    if 'foobar' in g.name:
        contacts.remove_group(g)
contacts.save()
osamu

I got the point. add_member() is a method of object Group().
Thanks!

osamu

@cvp one more question.
What is the attribute of People() that stores 'date' field?

cvp

@osamu you need to use ObjectiveC to get dates infos, like

from objc_util import *

# ObjectiveC contacts
CNContactStore = ObjCClass('CNContactStore').alloc().init()
CNContact = ObjCClass('CNContact')
Containers = CNContactStore.containersMatchingPredicate_error_(None,None)
for Container in Containers:
    id = Container.identifier()
    predicate = CNContact.predicateForContactsInContainerWithIdentifier_(id)
    # keys not exactly like in Apple doc
    predicate_contacts = CNContactStore.unifiedContactsMatchingPredicate_keysToFetch_error_(predicate, ['familyName', 'givenName', 'middleName', 'dates'], None)
    for contact in predicate_contacts:
        if len(contact.dates()) == 0:
          continue
        name = str(contact.givenName()) + '|' + str(contact.middleName()) + '|' + str(contact.familyName())
        print(name)
        for date in contact.dates():
          print(date)                   # CNLabeledValue
          d = date.value()
          print(d)                      # NSDateComponents
          print(date.label(),':',d.year(),d.month(),d.day())
osamu

@cvp Thanks! You know everything!
Oh, that's very challenging...

cvp

@osamu said:

You know everything!

Sincerely, not at all but I'm retired thus I have a lot of time to spend by searching, trying, bugging 😂

osamu

@cvp Could you give me an example to assign values?

cvp

@osamu said:

Could you give me an example to assign values?

Sorry, I don't understand: assign values to what?

ccc

Like setting a person's first name, last name, email address, mobile phone number, country, etc?
Reading a value vs. writing a value.

cvp

@osamu thanks @ccc for his help

Here, a sample to create a contact with his photo

# https://forum.omz-software.com/topic/2734/contacts-module-access-profile-picture/4
from objc_util import *
import photos

# Load classes we need from Contacts.framework:
NSBundle.bundleWithPath_('/System/Library/Frameworks/Contacts.framework').load()

def main():
    CNContactStore = ObjCClass('CNContactStore').alloc().init().autorelease()

    Containers = CNContactStore.containersMatchingPredicate_error_(None,None)
    for Container in Containers:
        containerId = Container.identifier()
        break                       # get first container

    all_assets =photos.get_assets()
    asset = photos.pick_asset(assets=all_assets)
    img_data_bytes = asset.get_image_data().getvalue()

    CNMutableContact = ObjCClass('CNMutableContact').alloc().init()
    CNMutableContact.firstName = 'aaTest'   # just to be the first
    CNMutableContact.imageData = img_data_bytes
    #CNMutableContact.phoneNumbers = ['123','456']
    print(dir(CNMutableContact))
    #return

    CNSaveRequest = ObjCClass('CNSaveRequest').new().autorelease()
    CNSaveRequest.addContact_toContainerWithIdentifier_(CNMutableContact, containerId)
    CNContactStore.executeSaveRequest_error_(CNSaveRequest, None)

if __name__ == '__main__':
    main()

osamu

@cvp Sorry, I’d like to know how to assign values to ‘date’ field of contact person.

JonB

You should take a look at the CNMutableContact created here, and you may be able to figure it out.

There should be an attributes dates, which according to the objc docs is an array of CNLabeledValue objects, where the value is an NSDate.

You can create NSDate using

dateobj=ObjCClass('NSDate').dateWithTimeIntervalSince1970_(time.mktime(time.strptime('2017-06-18', '%Y-%m-%d')))

CNLabeledValue=ObjCClass('CNLabeledValue')
value=CNLabeledValue.alloc().init()
value.label='birthday'
value.value=dateobj

Then you ought to be able to use

CNMutableContact.dates=[value]

cvp

As I didn't know which field you want to set, I let a print(dir(CNMutableContact)) in my script, so you could find your-self the name of the field..
But now, as @JonB did the job, all is good so

cvp

@JonB CNLabeledValue has to be initialized differently

    CNLabeledValue=ObjCClass('CNLabeledValue')
    value=CNLabeledValue.alloc()
    value.initWithLabel_value_('birthday',dateobj)

But date has to be a NSDateComponents

The app was terminated due to an Objective-C exception. Details below:

2021-02-02 23:04:50.219269
Labeled value <CNLabeledValue: 0x283090580: identifier=87E927A0-ED12-4AC9-89A8-EE6CCC75441D, label=birthday, value=2017-06-17 22:00:00 +0000, iOSLegacyIdentifier=-1> value 2017-06-17 22:00:00 +0000 has incorrect type __NSTaggedDate. It should be NSDateComponents.
cvp

That works

    dateComponents = ObjCClass('NSDateComponents').alloc().init()
    dateComponents.day = 4
    dateComponents.month = 5
    dateComponents.year = 2017

    CNLabeledValue=ObjCClass('CNLabeledValue')
    value=CNLabeledValue.alloc()
    value.initWithLabel_value_('birthday',dateComponents)
    print(dir(CNLabeledValue))
    CNMutableContact.dates=[value]
osamu

@cvp @JonB
Thank you folks.
I'm just a Python beginner and I had no place to begin w/ Objective C stuff. Now I think I can begin to try.

osamu

Now I’m trying to modify and save ‘dates’ field of an existing contact. Though the following script doesn’t spit errors but nothing happens to the contact. Could anybody help?

```
from objc_util import *

ObjectiveC contacts

CNContactStore = ObjCClass('CNContactStore').alloc().init()
CNContact = ObjCClass('CNContact')
Containers = CNContactStore.containersMatchingPredicate_error_(None,None)
for Container in Containers:
id = Container.identifier()
predicate = CNContact.predicateForContactsInContainerWithIdentifier_(id)
# keys not exactly like in Apple doc
predicate_contacts = CNContactStore.unifiedContactsMatchingPredicate_keysToFetch_error_(predicate, ['familyName', 'givenName', 'middleName', 'dates'], None)
'''
print('First name: ', end='')
first_name = input()
print('Last name: ', end='')
last_name = input()
'''
first_name = 'Ludwig'
last_name = 'Beethoven'
for contact in predicate_contacts:
if first_name == str(contact.givenName()) and last_name == str(contact.familyName()):
break
CNMutableContact = contact.mutableCopy()
dateComponents = ObjCClass('NSDateComponents').alloc().init()
label = 'passing'
dateComponents.day = 26
dateComponents.month = 3
dateComponents.year = 1827
'''
print('Label: ', end='')
label = input()
print(label.capitalize()+' year: ', end='')
dateComponents.year = int(input())
print(label.capitalize()+' month: ', end='')
dateComponents.month = int(input())
print(label.capitalize()+' day: ', end='')
dateComponents.day = int(input())
'''
CNLabeledValue=ObjCClass('CNLabeledValue')
value=CNLabeledValue.alloc()
value.initWithLabel_value_(label, dateComponents)
CNMutableContact.dates=[value]

CNSaveRequest = ObjCClass('CNSaveRequest').new().autorelease()
CNSaveRequest.updateContainer_(CNMutableContact)
CNContactStore.executeSaveRequest_error_(CNSaveRequest, None) ```
cvp

@osamu this works

# https://forum.omz-software.com/topic/2734/contacts-module-access-profile-picture/4
from objc_util import *
import photos
import time

# Load classes we need from Contacts.framework:
NSBundle.bundleWithPath_('/System/Library/Frameworks/Contacts.framework').load()

def main():
    CNContactStore = ObjCClass('CNContactStore').alloc().init().autorelease()

    Containers = CNContactStore.containersMatchingPredicate_error_(None,None)
    for Container in Containers:
        containerId = Container.identifier()
        break                       # get first container

    all_assets =photos.get_assets()
    asset = photos.pick_asset(assets=all_assets)
    img_data_bytes = asset.get_image_data().getvalue()

    CNMutableContact = ObjCClass('CNMutableContact').alloc().init()
    CNMutableContact.firstName = 'aaTest'   # just to be the first
    CNMutableContact.imageData = img_data_bytes
    #CNMutableContact.phoneNumbers = ['123','456']
    #print(dir(CNMutableContact))
    #return

    dateComponents = ObjCClass('NSDateComponents').alloc().init()
    dateComponents.day = 4
    dateComponents.month = 5
    dateComponents.year = 2017

    CNLabeledValue=ObjCClass('CNLabeledValue')
    value=CNLabeledValue.alloc()
    value.initWithLabel_value_('passing',dateComponents)
    print(dir(CNLabeledValue))
    CNMutableContact.dates=[value]

    CNSaveRequest = ObjCClass('CNSaveRequest').new().autorelease()
    CNSaveRequest.addContact_toContainerWithIdentifier_(CNMutableContact, containerId)
    CNContactStore.executeSaveRequest_error_(CNSaveRequest, None)

if __name__ == '__main__':
    main()

osamu

Wrong: CNSaveRequest.updateContainer_(CNMutableContact)
Correct: CNSaveRequest.updateContact(CNMutableContact)

Console:
First name: Ludwig
Last name: Beethoven
Label: passing
Passing year: 1827
Passing month: 3
Passing day: 26

Final code:

from objc_util import *

# ObjectiveC contacts
CNContactStore = ObjCClass('CNContactStore').alloc().init()
CNContact = ObjCClass('CNContact')
Containers = CNContactStore.containersMatchingPredicate_error_(None,None)
for Container in Containers:
    id = Container.identifier()
    predicate = CNContact.predicateForContactsInContainerWithIdentifier_(id)
    # keys not exactly like in Apple doc
    predicate_contacts = CNContactStore.unifiedContactsMatchingPredicate_keysToFetch_error_(predicate, ['familyName', 'givenName', 'middleName', 'dates'], None)
    print('First name: ', end='')
    first_name = input()
    print('Last name: ', end='')
    last_name = input()
    for contact in predicate_contacts:
        if first_name == str(contact.givenName()) and last_name == str(contact.familyName()):
            break

CNMutableContact = contact.mutableCopy()
dateComponents = ObjCClass('NSDateComponents').alloc().init()
print('Label: ', end='')
label = input()
print(label.capitalize()+' year: ', end='')
dateComponents.year = int(input())
print(label.capitalize()+' month: ', end='')
dateComponents.month = int(input())
print(label.capitalize()+' day: ', end='')
dateComponents.day = int(input())
CNLabeledValue=ObjCClass('CNLabeledValue')
value=CNLabeledValue.alloc()
value.initWithLabel_value_(label, dateComponents)
CNMutableContact.dates=[value]
CNSaveRequest = ObjCClass('CNSaveRequest').new().autorelease()
CNSaveRequest.updateContact(CNMutableContact)
CNContactStore.executeSaveRequest_error_(CNSaveRequest, None)

I couldn’t attach the capture 😣

cvp

@osamu you can also see this

# add a date field with custom label to an existing contact
#   of course extensible to all fields
from objc_util import *

def main():

    CNContactStore = ObjCClass('CNContactStore').alloc().init().autorelease()

    # get a particular contact  
    contact_name = 'aaTest'
    date_label   = 'my own label'
    date_ymd     = '20200201'
    CNContact = ObjCClass('CNContact')  
    predicate = CNContact.predicateForContactsMatchingName_(contact_name)
    predicate_contacts = CNContactStore.unifiedContactsMatchingPredicate_keysToFetch_error_(predicate, ['dates'], None)

    for contact in predicate_contacts:
        # Create a CNMutableContact by copy of an existing contact
        CNMutableContact = ObjCClass('CNMutableContact').alloc().init()
        CNMutableContact = contact.mutableCopy()

        # Create a date         
        NSDateComponents = ObjCClass('NSDateComponents').alloc().init()
        NSDateComponents.setDay_  (int(date_ymd[6:8]))
        NSDateComponents.setMonth_(int(date_ymd[4:6]))
        NSDateComponents.setYear_ (int(date_ymd[0:4]))

        # create a custom label
        CNLabeledValue = ObjCClass('CNLabeledValue').alloc()
        CNLabeledValue.initWithLabel_value_(date_label, NSDateComponents)

        # create a date with custom label       
        CNMutableContact.setDates_(ns([CNLabeledValue]))

        # save modified contact     
        CNSaveRequest = ObjCClass('CNSaveRequest').new().autorelease()
        CNSaveRequest.updateContact_(CNMutableContact)
        CNContactStore.executeSaveRequest_error_(CNSaveRequest, None)

if __name__ == '__main__':
    main()
osamu

Got it. For loop was no use.

cvp

@osamu 👍

cvp

@osamu said:

I couldn’t attach the capture 😣

See here

osamu

Thanks ❣️🙏
Beethoven

cvp

@osamu do you have his phone number ? 😂

osamu

Well, call him up and ask him to write Symphony No.10? 😂