In this example, we will write a simple SkypeKit-based SMS message sender.
To make things slightly more interesting, let's make the sender capable of taking multiple phone numbers as targets.
The first thing after login, would be to create an empty SMS object. This can be done with Skype::CreateOutgoingSms method, that reutrns us a valid SmsRef.
MySms::Ref sms; skype->CreateOutgoingSms(sms);
The next step is to set the message body text and target phone numbers (string list).
Sms::SETBODYRESULT setBodyResult; uint freeUntilNextChunk; SEStringList chunks; sms->SetBody("Test message", setBodyResult, chunks, freeUntilNextChunk); bool targetsSuccess; sms->SetTargets(targetList, targetsSuccess);
Note that the targetsSuccess return argument does not actually indicate that the targets were valid phone numbers. The Sms::SetTargets method is asynchronous - it sets the target list and then drops out.
However, once the targets are set - actual target validity checking will take place in background. This will manifest itself as a series of Sms::P_TARGET_STATUSES property change events (see MySms::OnChange).
Normal target status progression is:
In case of failure (per target), progression is:
Note that you do not quite know yet - what caused the TARGET_NOT_ROUTABLE state for the target. To get a better idea for what went wrong, you will need to actually send the SMS.
At this point you will have an Sms object with body text and targets set, and in background, targets are being analyzed for their validity. There is also no knowledge of the price the user needs to pay for the message. The price property will only gets set once all targets have reached a determined state (TARGET_ACCEPTABLE or TARGET_NOT_ROUTABLE).
So, at this point, you have two choises:
1. You can wait until all targets reach either TARGET_ACCEPTABLE or TARGET_NOT_ROUTABLE, so that you can display the SMS price to the user before sending out the message.
2. You can send out the message immediately, in which case the target analyze process will still need to finish and then the message will be sent.
In our case, we will wait for sms->canSend to turn true, but if you wish to go for a simpler case, you can comment out the while (!sms->canSend) loop and the waiting for keypress, and the code will still work.
Now our Sms object is ready for send. SMS send goes through Conversation class - as with all other communications. In this case, through Conversation::PostSms method.
ConversationRef conv; skype->GetConversationByParticipants(targetList, conv, true, false); conv->PostSMS(sms, "unused");
For detecting success, we have Sms::P_STATUS states we can check: SENT_TO_SERVER - message has been sent to server and DELIVERED - we have confirmation of delivery. These status changes we can catch in Sms::OnChane and have two MySms class variables set accordingly (isSent and is Delivered).
Advantage of isSent is that it's much quicker and generally makes more sense in full-blown UI. In our case, as sending a single SMS is the entire purpose of the "UI", we might as well wait until sms->isDelivered.
In case of successful send, the state sequence should go like this:
SMS TIMESTAMP value: <TIMESTAMP> SMS STATUS value: SENDING_TO_SERVER SMS CHATMSG_ID value: <MSG_ID> SMS TARGET <TARGET> STATUS = TARGET_DELIVERY_PENDING SMS TARGET <TARGET> STATUS = TARGET_ACCEPTABLE SMS TARGET <TARGET> STATUS = TARGET_DELIVERY_PENDING SMS STATUS value: SENT_TO_SERVER SMS TARGET <TARGET> STATUS = TARGET_DELIVERY_SUCCESSFUL SMS STATUS value: DELIVERED
As this example is a command-line client, it makes sense to wait until we get back either success or failure notice.
while ( (!sms->isDelivered) && (!sms->hasFailed) ) { Delay(1); };
sms->hasFailed is being set when Sms::P_STATUS turns FAILED.
Now that the SMS is sent, there remains the matter of displaying it in your Conversation UI. For this you can use the Sms::GetPropChatmsgId method, that returns a MessageRef that references Message object with Type POSTED_SMS. Once you have the Message object, you can retrieve its BODY_XML text. The body text will contain not only original SMS body but also various other fields, between XML tags.
Example Message body (with tabulation added):
<sms alt="Test message"> <type>2</type> <status>6</status> <failurereason>0</failurereason> <targets> <target status="6">+3725565678</target> </targets> <price>0.078 EUR</price> <body> <chunk id="0">Test message</chunk> <untilnext>148</untilnext> </body> <sendtimestamp>1298379016</sendtimestamp> </sms>
/**************************************************************************** Getting Started With SkypeKit. Tutorial Application, Step 13. In this example, we will write a simple SkypeKit-based SMS message sender. To make things slightly more interesting, let's make the sender capable of taking multiple phone numbers as targets. **/ #include <math.h> #include "skype-embedded_2.h" #include "keypair.h" #include "tutorial_common.h" using namespace Sid; SEString myAccountName; SEString myAccountPsw; SEStringList targetList; //---------------------------------------------------------------------------- // Interface section class MySms : public Sms { public: typedef DRef<MySms, Sms> Ref; typedef DRefs<MySms, Sms> Refs; bool canSend; // Set once all targets are either in TARGET_ACCEPTABLE or TARGET_NOT_ROUTABLE state bool isSent; // Set once Sms::P_STATUS goes to SENT_TO_SERVER bool hasFailed; // Set once Sms::P_STATUS goes to Sms::FAILED bool isDelivered; // Set once Sms::P_STATUS goes to Sms::DELIVERED MySms(unsigned int oid, SERootObject* root) : Sms(oid, root) { canSend = false; isSent = false; hasFailed = false; isDelivered = false; }; void OnChange(int prop); void PrintFailureReason(); }; class MySkype : public Skype { public: MySkype() : Skype() {}; Account* newAccount(int oid) {return new MyAccount(oid, this);} Sms* newSms(int oid) {return new MySms(oid, this);} }; //---------------------------------------------------------------------------- // Implementation section void MySms::PrintFailureReason() { Sms::FAILUREREASON problem; this->GetPropFailurereason(problem); printf("%s\n", (const char*)tostring(problem)); }; void MySms::OnChange(int prop) { // As target statuses for all targets are kept in one Sms propery (P_TARGET_STATUSES), // we need to loop through all targets in the list separately. if (prop == Sms::P_TARGET_STATUSES) { bool allTargetsAnalyzed = true; for (uint i=0; i < targetList.size(); i++) { SEString targetNumber; targetNumber = targetList[i]; Sms::TARGETSTATUS targetStatus; this->GetTargetStatus(targetNumber, targetStatus); SEString statusAsText = tostring(targetStatus); printf("%s - %s\n", (const char*)targetNumber, (const char*)statusAsText); allTargetsAnalyzed = allTargetsAnalyzed && ((targetStatus == Sms::TARGET_NOT_ROUTABLE) || (targetStatus == Sms::TARGET_ACCEPTABLE)); if (targetStatus == Sms::TARGET_NOT_ROUTABLE) { /** At this point, we already know that the SMS delivery will fail - because the target status was TARGET_NOT_ROUTABLE, so it looks like there is no reason to even attempt sending it. However, at this point we still don't know what was the reason behind "no routabilty" - The Sms FAILURE_REASON property will only get set during attempt to send it. One common cause of failure would be an attempt to send SMS messages from account with zero balance. That too would trigger TARGET_NOT_ROUTABLE. But your UI would not know this - as while in composing state, the SMS failure reason will remain empty. Thus, it actually makes sense -not- to stop the sending attempt here, post the SMS, knowing that it will fail, and then retrieve the actual error cause. **/ printf("WARNING: unroutable target %s!\n", (const char*)targetNumber); }; }; this->canSend = allTargetsAnalyzed; return; }; /** The price property as well, is common to all targets (targets may have different prices). The total price we can look at via Sms::GetPropPrice but to get to target specific prices we will need to loop through targets and use Sms::GetTargetPrice **/ if (prop == Sms::P_PRICE) { for (uint i=0; i < targetList.size(); i++) { /** The price information is kept in three properties: 1. price - integer, i.e. 123 2. precision - integer, i.e. 3 3. currency - string - i.e. "EUR" To calculate actual price value: price / 10^precision I.e. 123 / 10^3 = 0.123 EUR Note that the price value for any given target (and the total price of the SMS) will only become available once the target has reached TARGET_ACCEPTABLE status. Until then the price will remain at its default value: -1 **/ SEString targetNumber; targetNumber = targetList[i]; // Getting target price uint price; this->GetTargetPrice(targetNumber, price); if (price == MAX_UINT) { printf("SMS price for %s is still unknown!\n", (const char*)targetNumber); return; }; uint precision; this->GetPropPricePrecision(precision); SEString currency; this->GetPropPriceCurrency(currency); float f = precision; float actualPrice = price / pow(10, f); printf("SMS Price for %s is set to %2.3f %s\n", (const char*)targetNumber, actualPrice, (const char*)currency); // Getting SMS total price (we can reuse currency and precision) this->GetPropPrice(price); actualPrice = price / pow(10, f); printf("Total SMS price is now %2.3f %s\n", actualPrice, (const char*)currency); }; return; }; if (prop == Sms::P_STATUS) { Sms::STATUS status; this->GetPropStatus(status); this->isSent = (status == Sms::SENT_TO_SERVER); this->hasFailed = (status == Sms::FAILED); this->isDelivered = (status == Sms::DELIVERED); }; // Logging class / property / new value SEStringList dbg; SEString someProp; someProp = this->GetProp(prop); dbg = this->getPropDebug(prop, someProp); printf("%s %s value: %s\n", (const char*)dbg[0], (const char*)dbg[1], (const char*)dbg[2]); }; //---------------------------------------------------------------------------- // Main MySkype* skype = 0; int main(int argc, char * argv[]) { printf("*****************************************************************\n"); printf(" SkypeKit Tutorial, Step 13. - Sending SMS messages.\n"); printf("*****************************************************************\n"); if (argc < 4) { printf("usage: tutorial_13 <skypename> <password> <target> [target2]\n"); return 0; }; myAccountName = argv[1]; myAccountPsw = argv[2]; targetList.append(argv[3]); // Second SMS target is optional. if (argv[4]) { targetList.append(argv[4]); }; printf("Creating skype ..\n"); skype = new MySkype(); printf("Submitting application token..\n"); getKeyPair (); skype->init(keyBuf, inetAddr, portNum, "streamlog.txt"); skype->start(); printf("Retrieving account ..\n"); MyAccount::Ref account; if (skype->GetAccount(myAccountName, account)) { printf("Logging in..\n"); account->LoginWithPassword(myAccountPsw, false, true); account->BlockWhileLoggingIn(); printf("Loggin complete.\n"); printf("Press ENTER to compose SMS\n"); getchar(); // SMS objects are created with Skype::CreateOutgoingSms method. MySms::Ref sms; skype->CreateOutgoingSms(sms); // Sms::SetBody will attempt to set the message body text. Sms::SETBODYRESULT setBodyResult; uint freeUntilNextChunk; SEStringList chunks; sms->SetBody("Test message", setBodyResult, chunks, freeUntilNextChunk); if (setBodyResult != Sms::BODY_OK) { printf("Failed to set SMS body property.\n"); } else { printf("Number of SMS chunks used: %d, free characters until next chunk: %d\n", chunks.size(), freeUntilNextChunk); bool targetsSuccess; sms->SetTargets(targetList, targetsSuccess); if (!targetsSuccess) { printf("Failed to set SMS targets.\n"); } else { // canSend gets set from MySms::OnChange while (!sms->canSend) { Delay(1); }; printf("Press ENTER to send SMS\n"); getchar(); // While created in global scope, posting Sms objects goes through // conversations. ConversationRef conv; skype->GetConversationByParticipants(targetList, conv, true, false); // The body argument in PostSMS is currently unused. conv->PostSMS(sms, "unused"); /** For detecting success, we have two variables we can check: sms->isSent - message has been sent to server sms->isDelivered - we have confirmation of delivery. **/ while ( (!sms->isDelivered) && (!sms->hasFailed) ) { Delay(1); }; if (sms->hasFailed) { sms->PrintFailureReason(); }; // Printing out corresponding Message object body text MessageRef msg; sms->GetPropChatmsgId(msg); SEString msgBody; msg->GetPropBodyXml(msgBody); printf("\nCorresponding Message object body text:\n%s\n", (const char*)msgBody); }; }; printf("Press ENTER to quit.\n"); getchar(); /** Printing out remaining account balance. The logic here is the same as in case of Sms::P_PRICE, in MySms::OnChange **/ uint balance, precision; SEString currency; account->GetPropSkypeoutBalance(balance); account->GetPropSkypeoutBalanceCurrency(currency); account->GetPropSkypeoutPrecision(precision); float f = precision; float actualBalance = balance / pow(10, f); printf("Remaining balance on this account: %2.3f %s\n", actualBalance, (const char*)currency); printf("Logging out..\n"); account->Logout(false); account->BlockWhileLoggingOut(); printf("Logout complete.\n"); } else { printf("Account does not exist\n"); }; printf("Cleaning up.\n"); skype->stop(); delete skype; printf("Done.\n"); return 0; };
(c) Skype Technologies S.A. Confidential/Proprietary
Last updated: Fri Jan 27 2012