X-Git-Url: https://git.toastfreeware.priv.at/gregoa/bti.git/blobdiff_plain/e56e95dcd8468c9769e94d3a0dd1b429dec59ea6..4ebf8fe74926a8eb79b68fbf5acf82ce7e900255:/bti.c diff --git a/bti.c b/bti.c index 0ae103d..8243ad7 100644 --- a/bti.c +++ b/bti.c @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include #include "bti_version.h" @@ -36,17 +39,25 @@ #define dbg(format, arg...) \ do { \ if (debug) \ - printf("%s: " format , __func__ , ## arg ); \ + printf("%s: " format , __func__ , ## arg); \ } while (0) -static int debug = 0; +static int debug; enum host { HOST_TWITTER = 0, HOST_IDENTICA = 1, }; +enum action { + ACTION_UPDATE = 0, + ACTION_FRIENDS = 1, + ACTION_USER = 2, + ACTION_REPLIES = 4, + ACTION_PUBLIC = 8 +}; + struct session { char *password; char *account; @@ -55,24 +66,30 @@ struct session { char *time; char *homedir; char *logfile; + char *user; int bash; enum host host; + enum action action; }; struct bti_curl_buffer { char *data; + enum action action; int length; }; static void display_help(void) { - fprintf(stdout, "bti - send tweet to twitter\n"); + fprintf(stdout, "bti - send tweet to twitter or identi.ca\n"); fprintf(stdout, "Version: " BTI_VERSION "\n"); fprintf(stdout, "Usage:\n"); fprintf(stdout, " bti [options]\n"); fprintf(stdout, "options are:\n"); fprintf(stdout, " --account accountname\n"); fprintf(stdout, " --password password\n"); + fprintf(stdout, " --action action\n"); + fprintf(stdout, " ('update', 'friends', 'public', 'replies' or 'user')\n"); + fprintf(stdout, " --user screenname\n"); fprintf(stdout, " --proxy PROXY:PORT\n"); fprintf(stdout, " --host HOST\n"); fprintf(stdout, " --logfile logfile\n"); @@ -87,19 +104,6 @@ static void display_version(void) fprintf(stdout, "bti - version %s\n", BTI_VERSION); } -static char *get_string_from_stdin(void) -{ - static char *string = (char *)NULL; - if (string) { - free(string); - string = (char *)NULL; - } - - string = readline("tweet: "); - - return string; -} - static struct session *session_alloc(void) { struct session *session; @@ -120,10 +124,11 @@ static void session_free(struct session *session) free(session->proxy); free(session->time); free(session->homedir); + free(session->user); free(session); } -static struct bti_curl_buffer *bti_curl_buffer_alloc(void) +static struct bti_curl_buffer *bti_curl_buffer_alloc(enum action action) { struct bti_curl_buffer *buffer; @@ -139,6 +144,7 @@ static struct bti_curl_buffer *bti_curl_buffer_alloc(void) return NULL; } buffer->length = 0; + buffer->action = action; return buffer; } @@ -150,8 +156,17 @@ static void bti_curl_buffer_free(struct bti_curl_buffer *buffer) free(buffer); } -static const char *twitter_url = "https://twitter.com/statuses/update.xml"; -static const char *identica_url = "http://identi.ca/api/statuses/update.xml"; +static const char *twitter_user_url = "http://twitter.com/statuses/user_timeline/"; +static const char *twitter_update_url = "https://twitter.com/statuses/update.xml"; +static const char *twitter_public_url = "http://twitter.com/statuses/public_timeline.xml"; +static const char *twitter_friends_url = "https://twitter.com/statuses/friends_timeline.xml"; +static const char *twitter_replies_url = "http://twitter.com/statuses/replies.xml"; + +static const char *identica_user_url = "http://identi.ca/api/statuses/user_timeline/"; +static const char *identica_update_url = "http://identi.ca/api/statuses/update.xml"; +static const char *identica_public_url = "http://identi.ca/api/statuses/public_timeline.xml"; +static const char *identica_friends_url = "http://identi.ca/api/statuses/friends_timeline.xml"; +static const char *identica_replies_url = "http://identi.ca/api/statuses/replies.xml"; static CURL *curl_init(void) { @@ -168,6 +183,75 @@ static CURL *curl_init(void) return curl; } +void parse_statuses(xmlDocPtr doc, xmlNodePtr current) +{ + xmlChar *text = NULL; + xmlChar *user = NULL; + xmlNodePtr userinfo; + + current = current->xmlChildrenNode; + while (current != NULL) { + if (current->type == XML_ELEMENT_NODE) { + if (!xmlStrcmp(current->name, (const xmlChar *)"text")) + text = xmlNodeListGetString(doc, current->xmlChildrenNode, 1); + if (!xmlStrcmp(current->name, (const xmlChar *)"user")) { + userinfo = current->xmlChildrenNode; + while (userinfo != NULL) { + if ((!xmlStrcmp(userinfo->name, (const xmlChar *)"screen_name"))) { + if (user) + xmlFree(user); + user = xmlNodeListGetString(doc, userinfo->xmlChildrenNode, 1); + } + userinfo = userinfo->next; + } + } + if (user && text) { + printf("[%s] %s\n", user, text); + xmlFree(user); + xmlFree(text); + user = NULL; + text = NULL; + } + } + current = current->next; + } + + return; +} + +static void parse_timeline(char *document) +{ + xmlDocPtr doc; + xmlNodePtr current; + doc = xmlReadMemory(document, strlen(document), "timeline.xml", NULL, XML_PARSE_NOERROR); + + if (doc == NULL) + return; + + current = xmlDocGetRootElement(doc); + if (current == NULL) { + fprintf(stderr, "empty document\n"); + xmlFreeDoc(doc); + return; + } + + if (xmlStrcmp(current->name, (const xmlChar *) "statuses")) { + fprintf(stderr, "unexpected document type\n"); + xmlFreeDoc(doc); + return; + } + + current = current->xmlChildrenNode; + while (current != NULL) { + if ((!xmlStrcmp(current->name, (const xmlChar *)"status"))) + parse_statuses(doc, current); + current = current->next; + } + xmlFreeDoc(doc); + + return; +} + size_t curl_callback(void *buffer, size_t size, size_t nmemb, void *userp) { struct bti_curl_buffer *curl_buf = userp; @@ -187,16 +271,20 @@ size_t curl_callback(void *buffer, size_t size, size_t nmemb, void *userp) curl_buf->data = temp; memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size); curl_buf->length += buffer_size; + if (curl_buf->action) + parse_timeline(curl_buf->data); dbg("%s\n", curl_buf->data); return buffer_size; } -static int send_tweet(struct session *session) +static int send_request(struct session *session) { char user_password[500]; char data[500]; + /* is there usernames longer than 22 chars? */ + char user_url[70]; struct bti_curl_buffer *curl_buf; CURL *curl = NULL; CURLcode res; @@ -207,42 +295,94 @@ static int send_tweet(struct session *session) if (!session) return -EINVAL; - curl_buf = bti_curl_buffer_alloc(); + curl_buf = bti_curl_buffer_alloc(session->action); if (!curl_buf) return -ENOMEM; - snprintf(user_password, sizeof(user_password), "%s:%s", - session->account, session->password); - snprintf(data, sizeof(data), "status=\"%s\"", session->tweet); - curl = curl_init(); if (!curl) return -EINVAL; - curl_formadd(&formpost, &lastptr, - CURLFORM_COPYNAME, "status", - CURLFORM_COPYCONTENTS, session->tweet, - CURLFORM_END); + switch (session->action) { + case ACTION_UPDATE: + snprintf(user_password, sizeof(user_password), "%s:%s", + session->account, session->password); + snprintf(data, sizeof(data), "status=\"%s\"", session->tweet); + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "status", + CURLFORM_COPYCONTENTS, session->tweet, + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "source", + CURLFORM_COPYCONTENTS, "bti", + CURLFORM_END); + + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + slist = curl_slist_append(slist, "Expect:"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + switch (session->host) { + case HOST_TWITTER: + curl_easy_setopt(curl, CURLOPT_URL, twitter_update_url); + break; + case HOST_IDENTICA: + curl_easy_setopt(curl, CURLOPT_URL, identica_update_url); + break; + } + curl_easy_setopt(curl, CURLOPT_USERPWD, user_password); + + break; + case ACTION_FRIENDS: + snprintf(user_password, sizeof(user_password), "%s:%s", + session->account, session->password); + switch (session->host) { + case HOST_TWITTER: + curl_easy_setopt(curl, CURLOPT_URL, twitter_friends_url); + break; + case HOST_IDENTICA: + curl_easy_setopt(curl, CURLOPT_URL, identica_friends_url); + break; + } + curl_easy_setopt(curl, CURLOPT_USERPWD, user_password); - curl_formadd(&formpost, &lastptr, - CURLFORM_COPYNAME, "source", - CURLFORM_COPYCONTENTS, "bti", - CURLFORM_END); + break; + case ACTION_USER: + switch (session->host) { + case HOST_TWITTER: + sprintf(user_url, "%s%s.xml", twitter_user_url, session->user); + curl_easy_setopt(curl, CURLOPT_URL, user_url); + break; + case HOST_IDENTICA: + sprintf(user_url, "%s%s.xml", identica_user_url, session->user); + curl_easy_setopt(curl, CURLOPT_URL, user_url); + break; + } - curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + break; + case ACTION_REPLIES: + snprintf(user_password, sizeof(user_password), "%s:%s", + session->account, session->password); + switch (session->host) { + case HOST_TWITTER: + curl_easy_setopt(curl, CURLOPT_URL, twitter_replies_url); + break; + case HOST_IDENTICA: + curl_easy_setopt(curl, CURLOPT_URL, identica_replies_url); + break; + } + curl_easy_setopt(curl, CURLOPT_USERPWD, user_password); - switch (session->host) { - case HOST_TWITTER: - curl_easy_setopt(curl, CURLOPT_URL, twitter_url); - /* - * twitter doesn't like the "Expect: 100-continue" header - * anymore, so turn it off. - */ - slist = curl_slist_append(slist, "Expect:"); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); break; - case HOST_IDENTICA: - curl_easy_setopt(curl, CURLOPT_URL, identica_url); + case ACTION_PUBLIC: + switch (session->host) { + case HOST_TWITTER: + curl_easy_setopt(curl, CURLOPT_URL, twitter_public_url); + break; + case HOST_IDENTICA: + curl_easy_setopt(curl, CURLOPT_URL, identica_public_url); + break; + } + break; } @@ -251,7 +391,6 @@ static int send_tweet(struct session *session) if (debug) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); - curl_easy_setopt(curl, CURLOPT_USERPWD, user_password); dbg("user_password = %s\n", user_password); dbg("data = %s\n", data); @@ -261,12 +400,13 @@ static int send_tweet(struct session *session) curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf); res = curl_easy_perform(curl); if (res && !session->bash) { - fprintf(stderr, "error(%d) trying to send tweet\n", res); + fprintf(stderr, "error(%d) trying to perform operation\n", res); return -EINVAL; } curl_easy_cleanup(curl); - curl_formfree(formpost); + if (session->action == ACTION_UPDATE) + curl_formfree(formpost); bti_curl_buffer_free(curl_buf); return 0; } @@ -281,6 +421,8 @@ static void parse_configfile(struct session *session) char *host = NULL; char *proxy = NULL; char *logfile = NULL; + char *action = NULL; + char *user = NULL; char *file; /* config file is ~/.bti */ @@ -337,6 +479,16 @@ static void parse_configfile(struct session *session) c += 8; if (c[0] != '\0') logfile = strdup(c); + } else if (!strncasecmp(c, "action", 6) && + (c[6] == '=')) { + c += 7; + if (c[0] != '\0') + action = strdup(c); + } else if (!strncasecmp(c, "user", 4) && + (c[4] == '=')) { + c += 5; + if (c[0] != '\0') + user = strdup(c); } } while (!feof(config_file)); @@ -358,6 +510,22 @@ static void parse_configfile(struct session *session) } if (logfile) session->logfile = logfile; + if (action) { + if (strcasecmp(action, "update") == 0) + session->action = ACTION_UPDATE; + if (strcasecmp(action, "friends") == 0) + session->action = ACTION_FRIENDS; + if (strcasecmp(action, "user") == 0) + session->action = ACTION_USER; + if (strcasecmp(action, "replies") == 0) + session->action = ACTION_REPLIES; + if (strcasecmp(action, "public") == 0) + session->action = ACTION_PUBLIC; + free(action); + } + if (user) { + session->user = user; + } /* Free buffer and close file. */ free(line); @@ -394,10 +562,32 @@ static void log_session(struct session *session, int retval) break; } - if (retval) - fprintf(log_file, "%s: host=%s tweet failed\n", session->time, host); - else - fprintf(log_file, "%s: host=%s tweet=%s\n", session->time, host, session->tweet); + switch (session->action) { + case ACTION_UPDATE: + if (retval) + fprintf(log_file, "%s: host=%s tweet failed\n", + session->time, host); + else + fprintf(log_file, "%s: host=%s tweet=%s\n", + session->time, host, session->tweet); + break; + case ACTION_FRIENDS: + fprintf(log_file, "%s: host=%s retrieving friends timeline\n", + session->time, host); + break; + case ACTION_USER: + fprintf(log_file, "%s: host=%s retrieving %s's timeline\n", + session->time, host, session->user); + break; + case ACTION_REPLIES: + fprintf(log_file, "%s: host=%s retrieving replies\n", + session->time, host); + break; + case ACTION_PUBLIC: + fprintf(log_file, "%s: host=%s retrieving public timeline\n", + session->time, host); + break; + } fclose(log_file); } @@ -410,6 +600,8 @@ int main(int argc, char *argv[], char *envp[]) { "password", 1, NULL, 'p' }, { "host", 1, NULL, 'H' }, { "proxy", 1, NULL, 'P' }, + { "action", 1, NULL, 'A' }, + { "user", 1, NULL, 'u' }, { "logfile", 1, NULL, 'L' }, { "help", 0, NULL, 'h' }, { "bash", 0, NULL, 'b' }, @@ -423,12 +615,10 @@ int main(int argc, char *argv[], char *envp[]) int option; char *http_proxy; time_t t; -#if 0 - char *pwd = getenv("PWD"); - char *dir; -#endif + debug = 0; rl_bind_key('\t', rl_insert); + session = session_alloc(); if (!session) { fprintf(stderr, "no more memory...\n"); @@ -457,7 +647,7 @@ int main(int argc, char *argv[], char *envp[]) parse_configfile(session); while (1) { - option = getopt_long_only(argc, argv, "dqe:p:P:H:a:h", + option = getopt_long_only(argc, argv, "dqe:p:P:H:a:A:u:h", options, NULL); if (option == -1) break; @@ -483,6 +673,25 @@ int main(int argc, char *argv[], char *envp[]) session->proxy = strdup(optarg); dbg("proxy = %s\n", session->proxy); break; + case 'A': + if (strcasecmp(optarg, "update") == 0) + session->action = ACTION_UPDATE; + if (strcasecmp(optarg, "friends") == 0) + session->action = ACTION_FRIENDS; + if (strcasecmp(optarg, "user") == 0) + session->action = ACTION_USER; + if (strcasecmp(optarg, "replies") == 0) + session->action = ACTION_REPLIES; + if (strcasecmp(optarg, "public") == 0) + session->action = ACTION_PUBLIC; + dbg("action = %d\n", session->action); + break; + case 'u': + if (session->user) + free(session->user); + session->user = strdup(optarg); + dbg("user = %s\n", session->user); + break; case 'L': if (session->logfile) free(session->logfile); @@ -497,7 +706,7 @@ int main(int argc, char *argv[], char *envp[]) dbg("host = %d\n", session->host); break; case 'b': - session->bash= 1; + session->bash = 1; break; case 'h': display_help(); @@ -513,62 +722,56 @@ int main(int argc, char *argv[], char *envp[]) if (!session->account) { fprintf(stdout, "Enter twitter account: "); - session->account = get_string_from_stdin(); + session->account = readline(NULL); } if (!session->password) { fprintf(stdout, "Enter twitter password: "); - session->password = get_string_from_stdin(); + session->password = readline(NULL); } -#if 0 - /* get the current working directory basename */ - if (strcmp(pwd, home) == 0) - dir = "~"; - else { - dir = strrchr(pwd, '/'); - if (dir) - dir++; + + if (session->action == ACTION_UPDATE) { + if (session->bash) + tweet = readline(NULL); else - dir = "?"; - } -#endif - tweet = get_string_from_stdin(); - if (!tweet || strlen(tweet) == 0) { - dbg("no tweet?\n"); - return -1; - } + tweet = readline("tweet: "); + if (!tweet || strlen(tweet) == 0) { + dbg("no tweet?\n"); + return -1; + } -// session->tweet = zalloc(strlen(tweet) + strlen(dir) + 10); - session->tweet = zalloc(strlen(tweet) + 10); + session->tweet = zalloc(strlen(tweet) + 10); + if (session->bash) + sprintf(session->tweet, "$ %s", tweet); + else + sprintf(session->tweet, "%s", tweet); + + free(tweet); + dbg("tweet = %s\n", session->tweet); + } - /* if --bash is specified, add the "PWD $ " to - * the start of the tweet. */ - if (session->bash) -// sprintf(session->tweet, "%s $ %s", dir, tweet); - sprintf(session->tweet, "$ %s", tweet); - else - sprintf(session->tweet, "%s", tweet); - free(tweet); + if (!session->user) + session->user = session->account; dbg("account = %s\n", session->account); dbg("password = %s\n", session->password); - dbg("tweet = %s\n", session->tweet); dbg("host = %d\n", session->host); + dbg("action = %d\n", session->action); /* fork ourself so that the main shell can get on * with it's life as we try to connect and handle everything */ if (session->bash) { child = fork(); - if (child) { + if (child) { dbg("child is %d\n", child); exit(0); } } - retval = send_tweet(session); + retval = send_request(session); if (retval && !session->bash) - fprintf(stderr, "tweet failed\n"); + fprintf(stderr, "operation failed\n"); log_session(session, retval); exit: