X-Git-Url: https://git.toastfreeware.priv.at/gregoa/bti.git/blobdiff_plain/d945f656832ea5f9c816a93383dbd7ff3b6b2b94..4ebf8fe74926a8eb79b68fbf5acf82ce7e900255:/bti.c diff --git a/bti.c b/bti.c index 2119644..8243ad7 100644 --- a/bti.c +++ b/bti.c @@ -24,8 +24,13 @@ #include #include #include +#include #include #include +#include +#include +#include +#include #include "bti_version.h" @@ -34,33 +39,60 @@ #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; char *tweet; + char *proxy; + 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"); fprintf(stdout, " --bash\n"); fprintf(stdout, " --debug\n"); fprintf(stdout, " --version\n"); @@ -72,22 +104,6 @@ static void display_version(void) fprintf(stdout, "bti - version %s\n", BTI_VERSION); } -static char *get_string_from_stdin(void) -{ - char *temp; - char *string; - - string = zalloc(100); - if (!string) - return NULL; - - if (!fgets(string, 99, stdin)) - return NULL; - temp = strchr(string, '\n'); - *temp = '\0'; - return string; -} - static struct session *session_alloc(void) { struct session *session; @@ -105,10 +121,14 @@ static void session_free(struct session *session) free(session->password); free(session->account); free(session->tweet); + 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; @@ -124,6 +144,7 @@ static struct bti_curl_buffer *bti_curl_buffer_alloc(void) return NULL; } buffer->length = 0; + buffer->action = action; return buffer; } @@ -135,7 +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 *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) { @@ -152,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; @@ -171,61 +271,142 @@ 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; struct curl_httppost *formpost = NULL; struct curl_httppost *lastptr = NULL; + struct curl_slist *slist = NULL; 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); + + 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; + } + + 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); + + break; + 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; + } + + if (session->proxy) + curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy); - curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); - curl_easy_setopt(curl, CURLOPT_URL, twitter_url); 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); + dbg("proxy = %s\n", session->proxy); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback); 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; } @@ -237,13 +418,17 @@ static void parse_configfile(struct session *session) size_t len = 0; char *account = NULL; char *password = NULL; + char *host = NULL; + char *proxy = NULL; + char *logfile = NULL; + char *action = NULL; + char *user = NULL; char *file; - char *home = getenv("HOME"); /* config file is ~/.bti */ - file = alloca(strlen(home) + 7); + file = alloca(strlen(session->homedir) + 7); - sprintf(file, "%s/.bti", home); + sprintf(file, "%s/.bti", session->homedir); config_file = fopen(file, "r"); @@ -279,25 +464,145 @@ static void parse_configfile(struct session *session) c += 9; if (c[0] != '\0') password = strdup(c); + } else if (!strncasecmp(c, "host", 4) && + (c[4] == '=')) { + c += 5; + if (c[0] != '\0') + host = strdup(c); + } else if (!strncasecmp(c, "proxy", 5) && + (c[5] == '=')) { + c += 6; + if (c[0] != '\0') + proxy = strdup(c); + } else if (!strncasecmp(c, "logfile", 7) && + (c[7] == '=')) { + 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)); if (password) session->password = password; if (account) - session->account= account; + session->account = account; + if (host) { + if (strcasecmp(host, "twitter") == 0) + session->host = HOST_TWITTER; + if (strcasecmp(host, "identica") == 0) + session->host = HOST_IDENTICA; + free(host); + } + if (proxy) { + if (session->proxy) + free(session->proxy); + session->proxy = proxy; + } + 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); fclose(config_file); } +static void log_session(struct session *session, int retval) +{ + FILE *log_file; + char *filename; + char *host; + + /* Only log something if we have a log file set */ + if (!session->logfile) + return; + + filename = alloca(strlen(session->homedir) + + strlen(session->logfile) + 3); + + sprintf(filename, "%s/%s", session->homedir, session->logfile); + + log_file = fopen(filename, "a+"); + if (log_file == NULL) + return; + switch (session->host) { + case HOST_TWITTER: + host = "twitter"; + break; + case HOST_IDENTICA: + host = "identi.ca"; + break; + default: + host = "unknown"; + break; + } + + 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); +} + int main(int argc, char *argv[], char *envp[]) { static const struct option options[] = { { "debug", 0, NULL, 'd' }, { "account", 1, NULL, 'a' }, { "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' }, { "version", 0, NULL, 'v' }, @@ -306,24 +611,43 @@ int main(int argc, char *argv[], char *envp[]) struct session *session; pid_t child; char *tweet; - int retval; + int retval = 0; int option; -#if 0 - char *home = getenv("HOME"); - char *pwd = getenv("PWD"); - char *dir; -#endif + char *http_proxy; + time_t t; + + debug = 0; + rl_bind_key('\t', rl_insert); + session = session_alloc(); if (!session) { fprintf(stderr, "no more memory...\n"); return -1; } + /* get the current time so that we can log it later */ + time(&t); + session->time = strdup(ctime(&t)); + session->time[strlen(session->time)-1] = 0x00; + + session->homedir = strdup(getenv("HOME")); + curl_global_init(CURL_GLOBAL_ALL); + + /* Set environment variables first, before reading command line options + * or config file values. */ + http_proxy = getenv("http_proxy"); + if (http_proxy) { + if (session->proxy) + free(session->proxy); + session->proxy = strdup(http_proxy); + dbg("http_proxy = %s\n", session->proxy); + } + parse_configfile(session); while (1) { - option = getopt_long_only(argc, argv, "dqe:p:a:h", + option = getopt_long_only(argc, argv, "dqe:p:P:H:a:A:u:h", options, NULL); if (option == -1) break; @@ -343,8 +667,46 @@ int main(int argc, char *argv[], char *envp[]) session->password = strdup(optarg); dbg("password = %s\n", session->password); break; + case 'P': + if (session->proxy) + free(session->proxy); + 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); + session->logfile = strdup(optarg); + dbg("logfile = %s\n", session->logfile); + break; + case 'H': + if (strcasecmp(optarg, "twitter") == 0) + session->host = HOST_TWITTER; + if (strcasecmp(optarg, "identica") == 0) + session->host = HOST_IDENTICA; + dbg("host = %d\n", session->host); + break; case 'b': - session->bash= 1; + session->bash = 1; break; case 'h': display_help(); @@ -360,65 +722,59 @@ 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 (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) + 10); + if (session->bash) + sprintf(session->tweet, "$ %s", tweet); + else + sprintf(session->tweet, "%s", tweet); -// session->tweet = zalloc(strlen(tweet) + strlen(dir) + 10); - session->tweet = zalloc(strlen(tweet) + 10); + 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); - if (retval && !session->bash) { - fprintf(stderr, "tweet failed\n"); - return -1; - } + retval = send_request(session); + if (retval && !session->bash) + fprintf(stderr, "operation failed\n"); - session_free(session); + log_session(session, retval); exit: - return 0; + session_free(session); + return retval;; }