ChatKit.framework: Difference between revisions

From iPhone Development Wiki
(Add info on sending messages in iOS 7)
(→‎Sending a Message in iOS 8: Minor formatting issues)
 
(5 intermediate revisions by 3 users not shown)
Line 123: Line 123:
This should pretty much print all what is in the userInfo dictionary to the syslog.
This should pretty much print all what is in the userInfo dictionary to the syslog.


== Reading a Message ==
== Sending a Message in iOS 7 ==


As of iOS 7, you can use something like the following code to send a message programatically. It will automatically decide whether to send as an SMS or iMessage:
As of iOS 7, you can use something like the following code to send a message programatically. It will automatically decide whether to send as an SMS or iMessage:
Line 144: Line 144:


Note that this will only work from the MobileSMS process, as [CKConversationList sharedConversationList] is null in other processes.
Note that this will only work from the MobileSMS process, as [CKConversationList sharedConversationList] is null in other processes.
== Sending a Message in iOS 8 ==
On iOS 8 the API names were changed a bit. Now you can send an iMessage or SMS to a pre-existing conversation like this:
<source lang=objc>
//Get the shared conversation list
CKConversationList* conversationList = [CKConversationList sharedConversationList];
//Get the conversation for an address
CKConversation *conversation = [conversationList conversationForExistingChatWithGroupID:@"11111111"]; //"11111111" would be the receivers phone number
//now you could get some Information about the receiver:
CKEntity * recipient = [conversation recipient];
ABRecordRef* record =  (ABRecordRef*)[recipient abRecord];
NSString* firstName;
if(record != nil)
firstName = (NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty) ; //get the first name of the receiver
firstName = firstName ? firstName : @""; //there's a chance that there was no first name saved in the address book or no entry for this phone number in the address book at all!
//Make a new composition
NSAttributedString* text = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"Hello %@!", firstName]];
CKComposition* composition = [[CKComposition alloc] initWithText:text subject:nil];
//Add attachment (if desired).
NSURL* fileUrl = [NSURL URLWithString:@"file:///var/mobile/Media/DCIM/100APPLE/IMG_0001.JPG"];
CKMediaObjectManager* objManager = [%c(CKMediaObjectManager) sharedInstance];
CKMediaObject* object = [objManager mediaObjectWithFileURL:fileUrl filename:nil transcoderUserInfo:nil attributionInfo:@{} hideAttachment:NO];
composition = [composition compositionByAppendingMediaObject:object];
//A new message from the composition
CKMessage* message = [conversation messageWithComposition:composition];
//And finally, send the message in the conversation
[conversation sendMessage:message newComposition:YES];
</source>
If you haven't texted this person before, the conversation variable in the previous code will be nil. If that's the case, you can run the following to create their conversation and send a text to them:
<source lang=objc>
//Get the text
NSAttributedString* text = [[NSAttributedString alloc] initWithString:@"Hello World!"];
//Get your personal account
IMAccountController* sharedAccountController = [%c(IMAccountController) sharedInstance];
IMAccount* myAccount = [sharedAccountController mostLoggedInAccount];
//Create a handle for the recipient
NSString* address = @"+11231231234"; //Their phone number
__NSCFString* handleId = (__NSCFString *)address;
IMHandle* handle = [[%c(IMHandle) alloc] initWithAccount:myAccount ID:handleId alreadyCanonical:YES];
//Get the chat for the given handle
IMChatRegistry* registry = [%c(IMChatRegistry) sharedInstance];
IMChat* chat = [registry chatForIMHandle:handle];
//Create and send the message
IMMessage* message = [%c(IMMessage) instantMessageWithText:text flags:1048581]; // Flags is always 1048581 in a regular message. It is 19922949 in an instant recording
[chat sendMessage:message];
</source>
Note that this will still only work from the MobileSMS process, even through  [CKConversationList sharedConversationList] is NOT always null in other processes. If you need to send a message from another process, launch the MobileSMS App this way:
<source lang=objc>
[[UIApplication sharedApplication]launchApplicationWithIdentifier:@"com.apple.MobileSMS" suspended:YES];
</source>
and pass the message you'd like to send to it using IPC (use [https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFMessagePortRef/index.html CFMessagePort] or [http://iphonedevwiki.net/index.php/CPDistributedMessagingCenter CPDistributedMessagingCenter] in combination with [https://github.com/rpetrich/RocketBootstrap RocketBootstrap])
== Sending Messages through CTMessageCenter ==
Messages that are send using the APIs described above, will be saved in the MobileSMS App. If you don't need to save them, or if you want them to be invisible, you can send an iMessage/SMS using the following code:
<source lang=objc>
BOOL success = [[CTMessageCenter sharedMessageCenter] sendSMSWithText:@"Hello!" serviceCenter:nil toAddress:@"1111111"];
NSLog (@"Sending the message was %@", success ? @"successful" : @"unsuccessful");
</source>
Message send this way won't appear in the MobileSMS App. But you can only do this from processes which have the com.apple.coretelephony.Identity.get and the com.apple.CommCenter.Messages-send entitlement.


== Useful Links ==
== Useful Links ==

Latest revision as of 16:07, 30 August 2020

ChatKit.framework
Private Framework
Availabile 3.0 – present
Class Prefix CK
Headers [headers.cynder.me]

ChatKit is a framework designed for handling SMS, iMessage and MMS, and the views for these. iMessage was introduced in iOS 5 under the codename CKMadridService but has since been replaced and fully integrated into ChatKit.

Listener Capabilities

As of iOS 7, things a process can do with this framework is limited by imagent, the backend daemon that processes calls from ChatKit.framework. Imagent uses a property called listener capabilities to determine what each process can do.

Enum Declaration

enum FZListenerCapabilities {
    Status = 1 << 0,
    Notifications = 1 << 1,
    Chats = 1 << 2,
    VC = 1 << 3,
    AVChatInfo = 1 << 4,
    AuxInput = 1 << 5,
    VCInvitations = 1 << 6,
    Lega = 1 << 7,
    Transfers = 1 << 8,
    Accounts = 1 << 9,
    BuddyList = 1 << 10,
    ChatObserver = 1 << 11,
    SendMessages = 1 << 12,
    MessageHistory = 1 << 13,
    IDQueries = 1 << 14,
    ChatCounts = 1 << 15
};

Default Values for Some Common Processes

  • com.apple.springboard
    Status, Notifications, Accounts, Modify Read State, Chat Counts
  • com.apple.MobileSMS
    Status, Notifications, Chats, Transfers, Accounts, ID Queries

Reading a Message

First things first: When I speak about iMessage I include SMS as well.

ChatKit performs some actions when a message is read, but you as a Tweak developer can do even more.

When you read a message in the iMessage App, the notification CKConversationMessageReadNotification is posted.

We can simply listen to this notification using

[[NSNotificationCenter defaultCenter] addObserver:myTarget selector:@selector(readAwesomeMessage:) name:@"CKConversationMessageReadNotification" object:nil];

So whenever the notification is posted, we can control it in the -(void)readAwesomeMessage:(NSNotification *)notification; method.

By looking at the NSNotification documentation we can see that every NSNotification object has an -(NSDictionary *)userInfo method that returns information which was sent with the notification. This method can return NULL if there is no information available.

From my findings I can say that the userInfo dictionary contains a CKIMMessage object for the key CKMessageKey so basically

-(void)readAwesomeMessage:(NSNotification *)notif {

CKIMMessage *msg = notif.userInfo[@"CKMessageKey"]; 
//CKIMMessage *msg = [[notif userInfo] objectForKey:@"CKMessageKey"]; -->long way that does the same as the line above

//...
}

Now you have a CKIMMessage object to use. You should of course verify the object is not NULL before you try to access properties of it. You can have a quick look at CKIMMessage.h and you directly see that it contains a lot of information to use:

@property (nonatomic,retain) IMMessage * IMMessage; //another message object
@property (nonatomic,readonly) NSString * guid; //message id
@property (nonatomic,readonly) NSString * address; //email address to which the message was sent
@property (nonatomic,readonly) NSAttributedString * subject; //subject of the conversation
@property (assign,nonatomic) CKConversation * conversation; //get the conversation in which the message was read. contains a lot of information as well
@property (nonatomic,readonly) NSArray * parts; //a message contains of different parts (CKMessagePart objects)
@property (nonatomic,readonly) NSArray * recipients; //who are in the conversation?
@property (nonatomic,readonly) NSDate * date; //date of reading
@property (nonatomic,readonly) NSDate * timeRead; //time exactly of reading
@property (nonatomic,readonly) CKEntity * sender; //who sent the message? it contains a lot of information as well
@property (nonatomic,readonly) BOOL isiMessage; //which type of message?
@property (nonatomic,readonly) BOOL isSMS; //which type of message?
@property (nonatomic,readonly) BOOL isOutgoing; //are you sending it?
@property (nonatomic,readonly) BOOL isFromMe; //same
@property (nonatomic,readonly) BOOL hasAttachments; //does it contain images or videos?
@property (nonatomic,readonly) BOOL isToEmailAddress; 
...

We will focus on the message parts a little bit more. It allows you to go through the entire message and filter out different types of parts (Text, Images, Videos).

-(void)readAwesomeMessage:(NSNotification *)notif {

CKIMMessage *msg = notif.userInfo[@"CKMessageKey"]; 

if (msg) { //avoid to have an EXC_BAD_ACCESS exception - we avoid trying to access a NULL object

for (CKMessagePart *part in [msg parts]) { //look through all parts of the message - we can safely do that because no one writes messages with thousands of parts.
    
//have a look at http://developer.limneos.net/?framework=ChatKit.framework&header=CKMessagePart.h
//write your code here what you do with each message part.

} 

} 

}

As you can see, it is very easy to implement your own code when the user reads a message. If you want to find out more about the notification's userInfo, you can log it easily:

-(void)readAwesomeMessage:(NSNotification *)notif {

for (NSString *key in [notification.userInfo allKeys]) {
    id value = [notif.userInfo objectForKey:key];
    NSLog(@"Class: %@ - Value : %@",[value class],value);
}

}

This should pretty much print all what is in the userInfo dictionary to the syslog.

Sending a Message in iOS 7

As of iOS 7, you can use something like the following code to send a message programatically. It will automatically decide whether to send as an SMS or iMessage:

//Get the shared conversation list
CKConversationList* conversationList = [CKConversationList sharedConversationList];
//Get the conversation for an address
CKConversation* conversation = [conversationList conversationForExistingChatWithAddresses:[NSArray arrayWithObjects:@"11111111", nil]];
//Make a new composition
NSAttributedString* text = [[NSAttributedString alloc] initWithString:message[@"reply"]];
CKComposition* composition = [[CKComposition alloc] initWithText:text subject:nil];
//A new message from the composition
CKMessage* smsMessage = [conversation newMessageWithComposition:composition addToConversation:YES];
//And finally, send the message in the conversation
[conversation sendMessage:smsMessage newComposition:YES];

Note that this will only work from the MobileSMS process, as [CKConversationList sharedConversationList] is null in other processes.

Sending a Message in iOS 8

On iOS 8 the API names were changed a bit. Now you can send an iMessage or SMS to a pre-existing conversation like this:

//Get the shared conversation list
CKConversationList* conversationList = [CKConversationList sharedConversationList];
//Get the conversation for an address
CKConversation *conversation = [conversationList conversationForExistingChatWithGroupID:@"11111111"]; //"11111111" would be the receivers phone number

//now you could get some Information about the receiver:
CKEntity * recipient = [conversation recipient];
ABRecordRef* record =  (ABRecordRef*)[recipient abRecord];
NSString* firstName;
if(record != nil)
	firstName = (NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty) ; //get the first name of the receiver
firstName = firstName ? firstName : @""; //there's a chance that there was no first name saved in the address book or no entry for this phone number in the address book at all!

//Make a new composition
 NSAttributedString* text = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"Hello %@!", firstName]];
CKComposition* composition = [[CKComposition alloc] initWithText:text subject:nil];

//Add attachment (if desired).
NSURL* fileUrl = [NSURL URLWithString:@"file:///var/mobile/Media/DCIM/100APPLE/IMG_0001.JPG"];

CKMediaObjectManager* objManager = [%c(CKMediaObjectManager) sharedInstance];
CKMediaObject* object = [objManager mediaObjectWithFileURL:fileUrl filename:nil transcoderUserInfo:nil attributionInfo:@{} hideAttachment:NO];
composition = [composition compositionByAppendingMediaObject:object];

//A new message from the composition
CKMessage* message = [conversation messageWithComposition:composition];
//And finally, send the message in the conversation
[conversation sendMessage:message newComposition:YES];

If you haven't texted this person before, the conversation variable in the previous code will be nil. If that's the case, you can run the following to create their conversation and send a text to them:

//Get the text
NSAttributedString* text = [[NSAttributedString alloc] initWithString:@"Hello World!"];

//Get your personal account
IMAccountController* sharedAccountController = [%c(IMAccountController) sharedInstance];
IMAccount* myAccount = [sharedAccountController mostLoggedInAccount];

//Create a handle for the recipient
NSString* address = @"+11231231234"; //Their phone number
__NSCFString* handleId = (__NSCFString *)address;
IMHandle* handle = [[%c(IMHandle) alloc] initWithAccount:myAccount ID:handleId alreadyCanonical:YES];

//Get the chat for the given handle
IMChatRegistry* registry = [%c(IMChatRegistry) sharedInstance];
IMChat* chat = [registry chatForIMHandle:handle];

//Create and send the message
IMMessage* message = [%c(IMMessage) instantMessageWithText:text flags:1048581]; // Flags is always 1048581 in a regular message. It is 19922949 in an instant recording
[chat sendMessage:message];

Note that this will still only work from the MobileSMS process, even through [CKConversationList sharedConversationList] is NOT always null in other processes. If you need to send a message from another process, launch the MobileSMS App this way:

[[UIApplication sharedApplication]launchApplicationWithIdentifier:@"com.apple.MobileSMS" suspended:YES];

and pass the message you'd like to send to it using IPC (use CFMessagePort or CPDistributedMessagingCenter in combination with RocketBootstrap)

Sending Messages through CTMessageCenter

Messages that are send using the APIs described above, will be saved in the MobileSMS App. If you don't need to save them, or if you want them to be invisible, you can send an iMessage/SMS using the following code:

BOOL success = [[CTMessageCenter sharedMessageCenter] sendSMSWithText:@"Hello!" serviceCenter:nil toAddress:@"1111111"];
NSLog (@"Sending the message was %@", success ? @"successful" : @"unsuccessful");

Message send this way won't appear in the MobileSMS App. But you can only do this from processes which have the com.apple.coretelephony.Identity.get and the com.apple.CommCenter.Messages-send entitlement.

Useful Links