ABPresence¶
A PyObjC Example without documentation
Sources¶
ABPersonDisplayNameAdditions.py¶
import AddressBook
import Cocoa
import objc
class ABPerson(objc.Category(AddressBook.ABPerson)):
# Pull first and last name, organization, and record flags
# If the entry is a company, display the organization name instead
def displayName(self):
firstName = self.valueForProperty_(AddressBook.kABFirstNameProperty)
lastName = self.valueForProperty_(AddressBook.kABLastNameProperty)
companyName = self.valueForProperty_(AddressBook.kABOrganizationProperty)
flags = self.valueForProperty_(AddressBook.kABPersonFlags)
if flags is None:
flags = 0
if (flags & AddressBook.kABShowAsMask) == AddressBook.kABShowAsCompany:
if len(companyName):
return companyName
lastNameFirst = (
flags & AddressBook.kABNameOrderingMask
) == AddressBook.kABLastNameFirst
hasFirstName = firstName is not None
hasLastName = lastName is not None
if hasLastName and hasFirstName:
if lastNameFirst:
return Cocoa.NSString.stringWithString_(f"{lastName} {firstName}")
else:
return Cocoa.NSString.stringWithString_(f"{firstName} {lastName}")
if hasLastName:
return lastName
return firstName
def compareDisplayNames_(self, person):
return self.displayName().localizedCaseInsensitiveCompare_(person.displayName())
PeopleDataSource.py¶
import AddressBook
import Cocoa
import InstantMessage
import objc
from ServiceWatcher import kAddressBookPersonStatusChanged, kStatusImagesChanged
class PeopleDataSource(Cocoa.NSObject):
_abPeople = objc.ivar()
_imPersonStatus = objc.ivar() # Parallel array to abPeople
_table = objc.IBOutlet()
def awakeFromNib(self):
self._imPersonStatus = Cocoa.NSMutableArray.alloc().init()
# We don't need to query the status of everyone, we will wait for
# notifications of their status to arrive, so we just set them all up
# as offline.
self.setupABPeople()
Cocoa.NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
self,
b"abDatabaseChangedExternallyNotification:",
AddressBook.kABDatabaseChangedExternallyNotification,
None,
)
Cocoa.NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
self,
b"addressBookPersonStatusChanged:",
kAddressBookPersonStatusChanged,
None,
)
Cocoa.NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
self, b"statusImagesChanged:", kStatusImagesChanged, None
)
# This dumps all the status information and rebuilds the array against
# the current _abPeople
# Fairly expensive, so this is only done when necessary
def rebuildStatusInformation(self):
# Now scan through all the people, adding their status to the status
# cache array
for i, person in enumerate(self._abPeople):
# Let's assume they're offline to start
bestStatus = InstantMessage.IMPersonStatusOffline
for service in InstantMessage.IMService.allServices():
screenNames = service.screenNamesForPerson_(person)
for screenName in screenNames:
dictionary = service.infoForScreenName_(screenName)
status = dictionary.get(InstantMessage.IMPersonStatusKey)
if status is not None:
thisStatus = status
if (
InstantMessage.IMComparePersonStatus(bestStatus, thisStatus)
!= Cocoa.NSOrderedAscending
):
bestStatus = thisStatus
self._imPersonStatus[i] = bestStatus
self._table.reloadData()
# Rebuild status information for a given person, much faster than a full
# rebuild
def rebuildStatusInformationForPerson_(self, forPerson):
for i, person in enumerate(self._abPeople):
if person is forPerson:
bestStatus = InstantMessage.IMPersonStatusOffline
# Scan through all the services, taking the 'best' status we
# can find
for service in InstantMessage.IMService.allServices():
screenNames = service.screenNamesForPerson_(person)
# Ask for the status on each of their screen names
for screenName in screenNames:
dictionary = service.infoForScreenName_(screenName)
status = dictionary.get(InstantMessage.IMPersonStatusKey)
if status is not None:
thisStatus = status
if (
InstantMessage.IMComparePersonStatus(
bestStatus, thisStatus
)
!= Cocoa.NSOrderedAscending
):
bestStatus = thisStatus
self._imPersonStatus[i] = bestStatus
self._table.reloadData()
break
# Sets up all our internal data
def setupABPeople(self):
# Keep around a copy of all the people in the AB now
self._abPeople = (
AddressBook.ABAddressBook.sharedAddressBook().people().mutableCopy()
)
# Sort them by display name
self._abPeople.sortUsingSelector_("compareDisplayNames:")
# Assume everyone is offline.
self._imPersonStatus.removeAllObjects()
offlineNumber = InstantMessage.IMPersonStatusOffline
for _ in range(len(self._abPeople)):
self._imPersonStatus.append(offlineNumber)
# This will do a full flush of people in our AB Cache, along with
# rebuilding their status */
def reloadABPeople(self):
self.setupABPeople()
# Now recache all the status info, this will spawn a reload of the table
self.rebuildStatusInformation()
def numberOfRowsInTableView_(self, tableView):
if self._abPeople is None:
return 0
return len(self._abPeople)
def tableView_objectValueForTableColumn_row_(self, tableView, tableColumn, row):
identifier = tableColumn.identifier()
if identifier == "image":
status = self._imPersonStatus[row]
return Cocoa.NSImage.imageNamed_(
InstantMessage.IMService.imageNameForStatus_(status)
)
elif identifier == "name":
return self._abPeople[row].displayName()
return None
# Posted from ServiceWatcher
# The object of this notification is an ABPerson who's status has
# Changed
def addressBookPersonStatusChanged_(self, notification):
self.rebuildStatusInformationForPerson_(notification.object())
# Posted from ServiceWatcher
# We should reload the tableview, because the user has changed the
# status images that iChat is using.
def statusImagesChanged_(self, notification):
self._table.reloadData()
# If the AB database changes, force a reload of everyone
# We could look in the notification to catch differential updates, but
# for now this is fine.
def abDatabaseChangedExternallyNotification_(self, notification):
self.reloadABPeople()
ServiceWatcher.py¶
import AddressBook # noqa: F401
import Cocoa
import InstantMessage
kAddressBookPersonStatusChanged = "AddressBookPersonStatusChanged"
kStatusImagesChanged = "StatusImagesChanged"
class ServiceWatcher(Cocoa.NSObject):
def startMonitoring(self):
nCenter = InstantMessage.IMService.notificationCenter()
if nCenter is None:
return None
nCenter.addObserver_selector_name_object_(
self,
b"imPersonStatusChangedNotification:",
InstantMessage.IMPersonStatusChangedNotification,
None,
)
nCenter.addObserver_selector_name_object_(
self,
b"imStatusImagesChangedAppearanceNotification:",
InstantMessage.IMStatusImagesChangedAppearanceNotification,
None,
)
def stopMonitoring(self):
nCenter = InstantMessage.IMService.notificationCenter()
nCenter.removeObserver_(self)
def awakeFromNib(self):
self.startMonitoring()
# Received from IMService's custom notification center. Posted when a
# different user (screenName) logs in, logs off, goes away,
# and so on. This notification is for the IMService object. The user
# information dictionary will always contain an
# IMPersonScreenNameKey and an IMPersonStatusKey, and no others.
def imPersonStatusChangedNotification_(self, notification):
service = notification.object()
userInfo = notification.userInfo()
screenName = userInfo[InstantMessage.IMPersonScreenNameKey]
abPersons = service.peopleWithScreenName_(screenName)
center = Cocoa.NSNotificationCenter.defaultCenter()
for person in abPersons:
center.postNotificationName_object_(kAddressBookPersonStatusChanged, person)
# Received from IMService's custom notification center. Posted when the
# user changes their preferred images for displaying status.
# This notification is relevant to no particular object. The user
# information dictionary will not contain keys. Clients that display
# status information graphically (using the green/yellow/red dots) should
# call <tt>imageURLForStatus:</tt> to get the new image.
# See "Class Methods" for IMService in this document.
def imStatusImagesChangedAppearanceNotification_(self, notification):
Cocoa.NSNotificationCenter.defaultCenter().postNotificationName_object_(
kStatusImagesChanged, self
)
main.py¶
import ABPersonDisplayNameAdditions # noqa: F401
import PeopleDataSource # noqa: F401
import ServiceWatcher # noqa: F401
from PyObjCTools import AppHelper
AppHelper.runEventLoop()
setup.py¶
"""
Script for building the example.
Usage:
python3 setup.py py2app
"""
from setuptools import setup
setup(
name="PyABPresence",
app=["main.py"],
data_files=["English.lproj"],
setup_requires=[
"py2app",
"pyobjc-framework-Cocoa",
"pyobjc-framework-InstantMessage",
],
)