Last active
February 15, 2016 05:45
-
-
Save lukaskollmer/962625208f872c242706 to your computer and use it in GitHub Desktop.
Load Twitter profile pictures and set them as image to your iOS contacts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import twitter | |
import json | |
import urllib | |
import contacts | |
import console | |
from objc_util import * | |
import ui | |
import os | |
from PIL import Image | |
import webbrowser | |
console.clear() | |
CNContactStore = ObjCClass('CNContactStore') | |
CNContact = ObjCClass('CNContact') | |
CNSaveRequest = ObjCClass('CNSaveRequest') | |
UIActivityIndicatorView = ObjCClass('UIActivityIndicatorView') | |
activity_indicator = None | |
twitter_contacts = [] | |
current_contact_index = -1 | |
did_present = False | |
store = CNContactStore.alloc().init() | |
view = ui.load_view() | |
all_view_element_names = [ | |
'contact_name_label', | |
'old_image_title_label', | |
'new_image_title_label', | |
'left_image_view', | |
'right_image_view', | |
'keep_image_button', | |
'use_new_image_button', | |
'progress_view_container' | |
] | |
def setup_view(): | |
view['left_image_view'].content_mode = ui.CONTENT_SCALE_ASPECT_FIT | |
view['right_image_view'].content_mode = ui.CONTENT_SCALE_ASPECT_FIT | |
view['keep_image_button'].action = keep_current_image | |
view['use_new_image_button'].action = use_twitter_image | |
class NoTwitterAccountSetUpError(Exception): | |
pass | |
class LoadTwitterProfilePictureError(Exception): | |
def __init__(self, status_code): | |
self.status_code = status_code | |
def __str__(): | |
return 'Could not retrieve profile image (status: %i)' % (self.status_code,) | |
class Contact(object): | |
def __init__(self, name, twitter_username, has_image, current_image, twitter_image): | |
self.name = name | |
self.twitter_username = twitter_username | |
self.has_image = has_image | |
self.current_image = current_image | |
self.twitter_image = twitter_image | |
def __str__(self): | |
return 'Contact {} (@{})'.format(self.name, self.twitter_username) | |
def show_message_in_image_view(message, image_view): | |
if len(image_view.subviews) > 0: # already a message subview | |
for subview in image_view.subviews: | |
if type(subview) is ui.Label: | |
subview.text = message | |
else: | |
label = ui.Label() | |
label.frame = image_view.frame | |
label.text = message | |
label.text_color = 0.4 | |
label.center = ((image_view.width / 2), (image_view.height / 2)) | |
label.alignment = ui.ALIGN_CENTER | |
image_view.add_subview(label) | |
def profile_picture_for_account(username): | |
all_accounts = twitter.get_all_accounts() | |
if len(all_accounts) >= 1: | |
account = all_accounts[0] | |
parameters = {'screen_name': username} | |
status, data = twitter.request(account, 'https://api.twitter.com/1.1/users/show.json', 'GET', parameters) | |
if status == 200: | |
user_info = json.loads(data) | |
avatar_url = user_info['profile_image_url'] | |
# Get the URL for the original size: | |
avatar_url = avatar_url.replace('_normal', '') | |
filename, headers = urllib.urlretrieve(avatar_url) | |
img = Image.open(filename) | |
format = img.format.lower() | |
path = os.path.expanduser('~/Documents/{}.{}'.format(username, format)) | |
img.save(path, format=format) | |
image = ui.Image.named(path) | |
os.remove(path) | |
return image | |
else: | |
raise LoadTwitterProfilePictureError(status) | |
else: | |
raise NoTwitterAccountSetUpError('You don\'t have any Twitter accounts (or haven\'t given permission to access them).') | |
def addressBook_account_with_name(name): | |
predicate = CNContact.predicateForContactsMatchingName_(name) | |
keys_to_fetch = ['identifier', 'givenName', 'familyName', 'imageDataAvailable', 'imageData'] | |
result = store.unifiedContactsMatchingPredicate_keysToFetch_error_(predicate, keys_to_fetch, None)[0] | |
return result | |
def load_contacts_with_twitter(): | |
contacts_with_twitter = [] | |
all_people = contacts.get_all_people() | |
for person in all_people: | |
view['currently_loading_info_label'].text = 'Loading image for {}'.format(person.full_name) | |
social = person.social_profile | |
if social: | |
for service in social: | |
if service[1]['service'] == 'twitter': | |
twitter_username = service[1]['username'] | |
should_dismiss_view_because_no_account_error = False | |
try: | |
twitter_image = profile_picture_for_account(twitter_username) | |
except LoadTwitterProfilePictureError as error: | |
twitter_image = None | |
except NoTwitterAccountSetUpError as error: | |
twitter_image = None | |
should_dismiss_view_because_no_account_error = True | |
if console.alert(error.message, button1='open settings') == 1: | |
webbrowser.open('app-settings://') | |
view.close() | |
finally: | |
if should_dismiss_view_because_no_account_error == True: | |
view.close() | |
image = None | |
result = addressBook_account_with_name(person.full_name) | |
has_image = result.imageDataAvailable() | |
if has_image == True: | |
data = NSData.dataWithData_(result.imageData()) | |
image = UIImage.imageWithData_(data) | |
contacts_with_twitter.append(Contact(person.full_name, twitter_username, has_image, image, twitter_image)) | |
return contacts_with_twitter | |
def update_contact_with_name_set_new_image(name, image): | |
contact = addressBook_account_with_name(name).mutableCopy() | |
image_data = bytearray(image.to_png()) | |
contact.imageData = image_data | |
save_resuest = CNSaveRequest.new() | |
save_resuest.updateContact_(contact) | |
store.executeSaveRequest_error_(save_resuest, None) | |
def current_contact(): | |
if current_contact_index <= len(twitter_contacts): | |
return twitter_contacts[current_contact_index] | |
else: | |
return None | |
def show_next_contact(): | |
global current_contact_index | |
current_contact_index = current_contact_index + 1 | |
if current_contact_index >= len(twitter_contacts): | |
view.close() | |
return | |
contact = current_contact() | |
view['contact_name_label'].text = contact.name | |
view.right_button_items = [ui.ButtonItem(title='{}/{}'.format(current_contact_index+1, len(twitter_contacts)), tint_color=0.5)] | |
if contact.current_image is None: | |
left_message = 'No image set' | |
else: | |
left_message = '' | |
show_message_in_image_view(left_message, view['left_image_view']) | |
ObjCInstance(view['left_image_view']).image = contact.current_image | |
if contact.twitter_image is None: | |
right_message = 'Unable to load image' | |
else: | |
right_message = '' | |
show_message_in_image_view(right_message, view['right_image_view']) | |
view['right_image_view'].image = contact.twitter_image | |
view['use_new_image_button'].enabled = not contact.twitter_image == None | |
def keep_current_image(sender): | |
show_next_contact() | |
def use_twitter_image(sender): | |
contact = current_contact() | |
name = contact.name | |
new_image = view['right_image_view'].image | |
update_contact_with_name_set_new_image(name, new_image) | |
show_next_contact() | |
def set_all_views_hidden(hidden): | |
for element in all_view_element_names: | |
view[element].hidden = hidden | |
@on_main_thread | |
def show_ui(loading): | |
global did_present | |
global activity_indicator | |
if did_present == False: | |
try: | |
view.present('sheet') | |
did_present = True | |
except: | |
pass | |
set_all_views_hidden(loading) | |
if loading: | |
activity_indicator = UIActivityIndicatorView.alloc().initWithActivityIndicatorStyle_(2) #2= grey style | |
activity_indicator.startAnimating() | |
activity_indicator_container = ObjCInstance(view['progress_view_container']) | |
activity_indicator.center = activity_indicator_container.convertPoint_fromView_(activity_indicator_container.center(), activity_indicator_container.superview()) | |
activity_indicator_container.addSubview_(activity_indicator) | |
else: | |
activity_indicator.stopAnimating() | |
show_next_contact() | |
view['progress_view_container'].hidden = not loading | |
view['currently_loading_info_label'].hidden = not loading | |
if __name__ == '__main__': | |
setup_view() | |
show_ui(loading=True) | |
twitter_contacts = load_contacts_with_twitter() | |
show_ui(loading=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"selected" : false, | |
"frame" : "{{0, 0}, {540, 540}}", | |
"class" : "View", | |
"nodes" : [ | |
{ | |
"selected" : false, | |
"frame" : "{{6, 98}, {200, 384}}", | |
"class" : "ImageView", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"frame" : "{{220, 220}, {100, 100}}", | |
"class" : "ImageView", | |
"name" : "left_image_view", | |
"uuid" : "86F8DA1C-749C-4B49-9220-9ABBF87EC096" | |
} | |
}, | |
{ | |
"selected" : false, | |
"frame" : "{{334, 98}, {200, 384}}", | |
"class" : "ImageView", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"frame" : "{{220, 220}, {100, 100}}", | |
"class" : "ImageView", | |
"name" : "right_image_view", | |
"uuid" : "376D3E92-30F4-4775-BC07-7E3BC9DA7E28" | |
} | |
}, | |
{ | |
"selected" : false, | |
"frame" : "{{6, 6}, {528, 32}}", | |
"class" : "Label", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"font_size" : 20, | |
"frame" : "{{195, 254}, {150, 32}}", | |
"uuid" : "1B46EC9C-4220-43C7-AC1D-BFD961A95DC4", | |
"text" : "First Last Name", | |
"alignment" : "center", | |
"class" : "Label", | |
"name" : "contact_name_label", | |
"font_name" : "<System-Bold>" | |
} | |
}, | |
{ | |
"selected" : false, | |
"frame" : "{{6, 490}, {200, 32}}", | |
"class" : "Button", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"action" : "", | |
"frame" : "{{230, 254}, {80, 32}}", | |
"title" : "Keep this image", | |
"class" : "Button", | |
"uuid" : "64DC09A2-56F9-4DE9-8ADA-3B0FE7F765D0", | |
"font_size" : 15, | |
"name" : "keep_image_button" | |
} | |
}, | |
{ | |
"selected" : false, | |
"frame" : "{{334, 490}, {200, 32}}", | |
"class" : "Button", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"action" : "", | |
"frame" : "{{230, 254}, {80, 32}}", | |
"title" : "Use this image", | |
"class" : "Button", | |
"uuid" : "72C2D520-78A0-4D13-9E6B-0C5392695AD7", | |
"font_size" : 15, | |
"name" : "use_new_image_button" | |
} | |
}, | |
{ | |
"selected" : true, | |
"frame" : "{{6, 58}, {200, 32}}", | |
"class" : "Label", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"font_name" : "<System-Bold>", | |
"frame" : "{{195, 254}, {150, 32}}", | |
"uuid" : "19F5E89D-4A0B-45D7-BE95-6B37F20B9CA3", | |
"text" : "Address Book image", | |
"alignment" : "center", | |
"class" : "Label", | |
"name" : "old_image_title_label", | |
"font_size" : 18 | |
} | |
}, | |
{ | |
"selected" : false, | |
"frame" : "{{334, 58}, {200, 32}}", | |
"class" : "Label", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"name" : "new_image_title_label", | |
"frame" : "{{195, 254}, {150, 32}}", | |
"uuid" : "19F5E89D-4A0B-45D7-BE95-6B37F20B9CA3", | |
"text" : "Twitter Profile image", | |
"alignment" : "center", | |
"class" : "Label", | |
"font_size" : 18, | |
"font_name" : "<System-Bold>" | |
} | |
}, | |
{ | |
"selected" : false, | |
"frame" : "{{226, 220}, {100, 100}}", | |
"class" : "View", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"frame" : "{{220, 220}, {100, 100}}", | |
"class" : "View", | |
"name" : "progress_view_container", | |
"uuid" : "E7992A6A-86F9-4DB8-8BE8-8A055D3BA441" | |
} | |
}, | |
{ | |
"selected" : false, | |
"frame" : "{{6, 450}, {528, 32}}", | |
"class" : "Label", | |
"nodes" : [ | |
], | |
"attributes" : { | |
"font_name" : "<System>", | |
"frame" : "{{195, 254}, {150, 32}}", | |
"uuid" : "B589C3B7-ECBC-4E73-A163-1FA7A874B638", | |
"text" : "Label", | |
"alignment" : "center", | |
"class" : "Label", | |
"font_size" : 18, | |
"name" : "currently_loading_info_label" | |
} | |
} | |
], | |
"attributes" : { | |
"border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", | |
"enabled" : true, | |
"background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", | |
"name" : "Update Contact Pictures", | |
"tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", | |
"flex" : "" | |
} | |
} | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment