之前一直有用户要求写一个libofetion的demo,再加上很多用户对于纯命令行版本飞信的强烈需求,于是我昨天简单地写了一个demo,把libofetion的API也做了一些修改,使它用起来更像是一个lib,不过对于第三方开发的话还是有很多很难理解的地方,因为最初并没有想把它当做一个lib来发布。到现在我对飞信的开发又要暂时先告一段落了,周末都在openfetion和娱乐中度过的,实验室项目和论文又要开始提上日程了,OK,先把code列出来,再做下简单地说明
/***************************************************************************
* Copyright (C) 2010 by lwp *
* levin108@gmail.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include
#include
#include
#include
#include
#include
#define BUFLEN 1024
int password_inputed = 0;
int mobileno_inputed = 0;
int tono_inputed = 0;
int message_inputed = 0;
User *user;
pthread_t th;
static void usage(char *argv[]);
int fx_login(const char *mobileno, const char *password)
{
Config *config;
FetionConnection *tcp;
FetionSip *sip;
char *res;
char *nonce;
char *key;
char *aeskey;
char *response;
int local_group_count;
int local_buddy_count;
int group_count;
int buddy_count;
int ret;
/* construct a user object */
user = fetion_user_new(mobileno, password);
/* construct a config object */
config = fetion_config_new();
/* attach config to user */
fetion_user_set_config(user, config);
/* start ssi authencation,result string needs to be freed after use */
res = ssi_auth_action(user);
/* parse the ssi authencation result,if success,user's sipuri and userid
* are stored in user object,orelse user->loginStatus was marked failed */
parse_ssi_auth_response(res, user);
free(res);
/* whether needs to input a confirm code,or login failed
* for other reason like password error */
if(USER_AUTH_NEED_CONFIRM(user) || USER_AUTH_ERROR(user)) {
debug_error("authencation failed");
return 1;
}
/* initialize configuration for current user */
if(fetion_user_init_config(user) == -1) {
debug_error("initialize configuration");
return 1;
}
if(fetion_config_download_configuration(user) == -1) {
debug_error("download configuration");
return 1;
}
/* set user's login state to be hidden */
fetion_user_set_st(user, P_HIDDEN);
/* load user information and contact list information from local host */
fetion_user_load(user);
fetion_contact_load(user, &local_group_count, &local_buddy_count);
/* construct a tcp object and connect to the sipc proxy server */
tcp = tcp_connection_new();
if((ret = tcp_connection_connect(tcp, config->sipcProxyIP, config->sipcProxyPort)) == -1) {
debug_error("connect sipc server %s:%d\n", config->sipcProxyIP, config->sipcProxyPort);
return 1;
}
/* construct a sip object with the tcp object and attach it to user object */
sip = fetion_sip_new(tcp, user->sId);
fetion_user_set_sip(user, sip);
/* register to sipc server */
if(!(res = sipc_reg_action(user))) {
debug_error("register to sipc server");
return 1;
}
parse_sipc_reg_response(res, &nonce, &key);
free(res);
aeskey = generate_aes_key();
response = generate_response(nonce, user->userId, user->password, key, aeskey);
free(nonce);
free(key);
free(aeskey);
/* sipc authencation,you can printf res to see what you received */
if(!(res = sipc_aut_action(user, response))) {
debug_error("sipc authencation");
return 1;
}
if(parse_sipc_auth_response(res, user, &group_count, &buddy_count) == -1) {
debug_error("authencation failed");
return 1;
}
free(res);
free(response);
if(USER_AUTH_ERROR(user) || USER_AUTH_NEED_CONFIRM(user)) {
debug_error("login failed");
return 1;
}
/* save the user information and contact list information back to the local database */
fetion_user_save(user);
fetion_contact_save(user);
/* these... fuck the fetion protocol */
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
char buf[1024];
if(setsockopt(user->sip->tcp->socketfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
debug_error("settimeout");
return 1;
}
tcp_connection_recv(user->sip->tcp, buf, sizeof(buf));
return 0;
}
int send_message(const char *mobileno, const char *receiveno, const char *message)
{
Conversation *conv;
Contact *contact;
Contact *contact_cur;
Contact *target_contact = NULL;
int daycount;
int monthcount;
/* send this message to yourself */
if(*receiveno == '\0' || strcmp(receiveno, mobileno) == 0) {
/* construct a conversation object with the sipuri to set NULL
* to send a message to yourself */
conv = fetion_conversation_new(user, NULL, NULL);
if(fetion_conversation_send_sms_to_myself_with_reply(conv, message) == -1) {
debug_error("send message \"%s\" to %s", message, user->mobileno);
return 1;
}
}else{
/* get the contact detail information by mobile number,
* note that the result doesn't contain sipuri */
contact = fetion_contact_get_contact_info_by_no(user, receiveno, MOBILE_NO);
if(!contact) {
debug_error("get contact information of %s", receiveno);
return 1;
}
/* find the sipuri of the target user */
foreach_contactlist(user->contactList, contact_cur) {
if(strcmp(contact_cur->userId, contact->userId) == 0) {
target_contact = contact_cur;
break;
}
}
if(!target_contact) {
debug_error("sorry,maybe %s isn't in your contact list");
return 1;
}
/* do what the function name says */
conv = fetion_conversation_new(user, target_contact->sipuri, NULL);
if(fetion_conversation_send_sms_to_phone_with_reply(conv, message, &daycount, &monthcount) == -1) {
debug_error("send sms to %s", receiveno);
return 1;
}else{
debug_info("successfully send sms to %s\nyou have sent %d messages today, %d messages this monthcount",
receiveno, daycount, monthcount);
return 0;
}
}
return 0;
}
int main(int argc, char *argv[])
{
int ch;
char mobileno[BUFLEN];
char password[BUFLEN];
char receiveno[BUFLEN];
char message[BUFLEN];
memset(mobileno, 0, sizeof(mobileno));
memset(password, 0, sizeof(password));
memset(receiveno, 0, sizeof(receiveno));
memset(message, 0, sizeof(message));
while((ch = getopt(argc, argv, "f:p:t:d:")) != -1) {
switch(ch) {
case 'f':
mobileno_inputed = 1;
strncpy(mobileno, optarg, sizeof(mobileno) - 1);
break;
case 'p':
password_inputed = 1;
strncpy(password, optarg, sizeof(password) - 1);
break;
case 't':
tono_inputed = 1;
strncpy(receiveno, optarg, sizeof(receiveno) - 1);
break;
case 'd':
message_inputed = 1;
strncpy(message, optarg, sizeof(message) - 1);
break;
default:
break;
}
}
if(!mobileno_inputed || !password_inputed || !message_inputed) {
usage(argv);
return 1;
}
if(fx_login(mobileno, password))
return 1;
if(send_message(mobileno, receiveno, message))
return 1;
fetion_user_free(user);
return 0;
}
static void usage(char *argv[])
{
fprintf(stderr, "Usage:%s -f mobileno -p password -t receive_mobileno -d message\n", argv[0]);
}首先需要libofetion的支持,因为用到了最新的API,所以需要从hg中clone最新版本编译安装后才可编译该程序:
hg clone https://ofetion.googlecode.com/hg/ ofetion编译方法如下:
gcc -o cli cli.c `pkg-config --cflags --libs ofetion`有一个地方需要说明,请大家找到/* these… fuck the fetion protocol */这个句注释,它下面的几句话的作用是这样的,飞信在用户完成身份验证之后订阅相关信息之前会推送过来一条信令:
BN 406472150 SIP-C/4.0
N: SyncUserInfoV4
I: 2
Q: 1 BN
L: 124
用它来更新用户的积分等级之类的玩意,像这种浮云般的信令都被我直接忽略掉了,但在这种命令行模式纯粹为了发短信的情况下,不需要新建线程监听服务器推送过来的信息,但这条推送过来的信令就有可能影响其它信令的交互,而且更让人蛋疼的是并不是每次登录都会推送这条信令,有时候有,有时候没有,所以就加了在登录完成之后加了一个recv,设定了1s的超时,来处理这句信令,如果在网络情况不好的情况下,大家可以自己把超时时间设长一些,这样也就意味着在发送一条短信时在recv那里要停留几秒钟,否则就有可能导致信息发送失败。
下载地址:fetion-demo