From: gregor herrmann Date: Thu, 12 Mar 2009 15:55:34 +0000 (-0000) Subject: New upstream release; from the RELEASE-NOTES: X-Git-Tag: debian/015-1~4 X-Git-Url: https://git.toastfreeware.priv.at/debian/bti.git/commitdiff_plain/f013c76b7f5bcc834940f716a829a680ce8cd7dd New upstream release; from the RELEASE-NOTES: "--action support, bti now can output data from the twitter and identica servers". --- diff --git a/ChangeLog b/ChangeLog index 2822668..e96b9ef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +Summary of changes from v014 to v015 +============================================ + +Amir Mohammad Saied (11): + Actions support for bti + libxml2 libs and cflags + action and values for bash completion + Added action option in example bti config file + User action + Replies action + Updating bashcompletion and example config file for new actions + logging more + Updating documents + Handling unknown actions + Fixing an assignment issue + +Greg Kroah-Hartman (2): + Change the formatting of --action output + fix readline mess when in --bash mode + Summary of changes from v013 to v014 ============================================ diff --git a/Makefile b/Makefile index d2c9031..994e5e3 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ # # -VERSION = 014 +VERSION = 015 PROGRAM = bti @@ -34,7 +34,8 @@ CC = $(CROSS_COMPILE)gcc LD = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar -override CFLAGS += -g -Wall -pipe -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -O2 +XML2_CFLAGS = `xml2-config --cflags` +override CFLAGS += -g -Wall -pipe -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -O2 $(XML2_CFLAGS) WARNINGS = -Wstrict-prototypes -Wsign-compare -Wshadow \ -Wchar-subscripts -Wmissing-declarations -Wnested-externs \ @@ -60,13 +61,14 @@ export E Q # We need -lpthread for the pthread example #LIB_OBJS = -lcurl -lnsl -lssl -lcrypto LIB_OBJS = -lcurl -lnsl -lreadline +LIB_XML2 = `xml2-config --libs` all: $(PROGRAM) $(MAN_PAGES) # "Static Pattern Rule" to build all programs bti: %: $(HEADERS) $(GEN_HEADERS) $(CORE_OBJS) $(E) " LD " $@ - $(Q) $(LD) $(LDFLAGS) $(CORE_OBJS) -o $@ $(LIB_OBJS) + $(Q) $(LD) $(LDFLAGS) $(CORE_OBJS) -o $@ $(LIB_OBJS) $(LIB_XML2) # build the objects diff --git a/RELEASE-NOTES b/RELEASE-NOTES index b5c8fa6..533b88e 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,3 +1,9 @@ +bti 015 +============= +--action support, bti now can output data from the twitter +and identica servers! Thanks to Amir Saied for the work he put into this. +minor bugfixes for bash mode, bah, readline is a pain at times. + bti 014 ============= Fix reported readline bugs diff --git a/bti-bashcompletion b/bti-bashcompletion index fa155bc..3b0f4d4 100644 --- a/bti-bashcompletion +++ b/bti-bashcompletion @@ -5,15 +5,19 @@ _bti() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ "${cur}" == -* ]] ; then - COMPREPLY=( $(compgen -W "-a -p -P -H -b -d -v -h --account \ - --password --proxy --host --bash --debug --version \ - --help" -- ${cur}) ) + COMPREPLY=( $(compgen -W "-a -A -p -P -H -b -d -v -h + --account --action --password --proxy --host --bash \ + --user --debug --version --help" -- ${cur}) ) fi if [[ "${prev}" == "--host" ]] ; then COMPREPLY=( $(compgen -W "twitter identica" -- ${cur} ) ) fi + if [[ "${prev}" == "--action" ]] ; then + COMPREPLY=( $(compgen -W "friends public update user replies" -- ${cur} ) ) + fi + return 0 } diff --git a/bti.c b/bti.c index 2f3b35a..8f09356 100644 --- a/bti.c +++ b/bti.c @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include #include "bti_version.h" @@ -47,6 +50,15 @@ enum host { HOST_IDENTICA = 1, }; +enum action { + ACTION_UPDATE = 0, + ACTION_FRIENDS = 1, + ACTION_USER = 2, + ACTION_REPLIES = 4, + ACTION_PUBLIC = 8, + ACTION_UNKNOWN = 16 +}; + struct session { char *password; char *account; @@ -55,24 +67,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"); @@ -107,10 +125,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; @@ -126,6 +145,7 @@ static struct bti_curl_buffer *bti_curl_buffer_alloc(void) return NULL; } buffer->length = 0; + buffer->action = action; return buffer; } @@ -137,8 +157,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) { @@ -155,6 +184,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; @@ -174,16 +272,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; @@ -194,42 +296,96 @@ 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); - curl_formadd(&formpost, &lastptr, - CURLFORM_COPYNAME, "source", - CURLFORM_COPYCONTENTS, "bti", - CURLFORM_END); + 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_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + 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; + } - 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_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; + default: break; } @@ -238,7 +394,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); @@ -248,12 +403,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; } @@ -268,6 +424,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 */ @@ -324,6 +482,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)); @@ -345,6 +513,24 @@ static void parse_configfile(struct session *session) } if (logfile) session->logfile = logfile; + if (action) { + if (strcasecmp(action, "update") == 0) + session->action = ACTION_UPDATE; + else if (strcasecmp(action, "friends") == 0) + session->action = ACTION_FRIENDS; + else if (strcasecmp(action, "user") == 0) + session->action = ACTION_USER; + else if (strcasecmp(action, "replies") == 0) + session->action = ACTION_REPLIES; + else if (strcasecmp(action, "public") == 0) + session->action = ACTION_PUBLIC; + else + session->action = ACTION_UNKNOWN; + free(action); + } + if (user) { + session->user = user; + } /* Free buffer and close file. */ free(line); @@ -381,16 +567,54 @@ static void log_session(struct session *session, int retval) break; } - if (retval) - fprintf(log_file, "%s: host=%s tweet failed\n", + 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); - else - fprintf(log_file, "%s: host=%s tweet=%s\n", - session->time, host, session->tweet); + break; + default: + break; + } fclose(log_file); } +static char *get_string_from_stdin(void) +{ + char *temp; + char *string; + + string = zalloc(1000); + if (!string) + return NULL; + + if (!fgets(string, 999, stdin)) + return NULL; + temp = strchr(string, '\n'); + *temp = '\0'; + return string; +} + int main(int argc, char *argv[], char *envp[]) { static const struct option options[] = { @@ -399,6 +623,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' }, @@ -444,7 +670,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; @@ -470,6 +696,27 @@ 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; + else if (strcasecmp(optarg, "friends") == 0) + session->action = ACTION_FRIENDS; + else if (strcasecmp(optarg, "user") == 0) + session->action = ACTION_USER; + else if (strcasecmp(optarg, "replies") == 0) + session->action = ACTION_REPLIES; + else if (strcasecmp(optarg, "public") == 0) + session->action = ACTION_PUBLIC; + else + session->action = ACTION_UNKNOWN; + 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); @@ -498,6 +745,12 @@ int main(int argc, char *argv[], char *envp[]) } } + if (session->action == ACTION_UNKNOWN) { + fprintf(stderr, "Unknown action, valid actions are:\n"); + fprintf(stderr, "'update', 'friends', 'public', 'replies' or 'user'.\n"); + goto exit; + } + if (!session->account) { fprintf(stdout, "Enter twitter account: "); session->account = readline(NULL); @@ -508,44 +761,48 @@ int main(int argc, char *argv[], char *envp[]) session->password = readline(NULL); } - if (session->bash) - tweet = readline(NULL); - else - tweet = readline("tweet: "); - if (!tweet || strlen(tweet) == 0) { - dbg("no tweet?\n"); - return -1; - } + if (session->action == ACTION_UPDATE) { + if (session->bash) + tweet = get_string_from_stdin(); + else + tweet = readline("tweet: "); + if (!tweet || strlen(tweet) == 0) { + dbg("no tweet?\n"); + return -1; + } - 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", tweet); - else - sprintf(session->tweet, "%s", tweet); - free(tweet); + if (!session->user) + session->user = strdup(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: diff --git a/bti.example b/bti.example index bc97329..e4bbad8 100644 --- a/bti.example +++ b/bti.example @@ -7,4 +7,6 @@ account=twitmaster password=icanhascheezburger host=identica logfile=.bti.log +#action=update +#user=gregkh #proxy=http://localhost:8080 diff --git a/bti.xml b/bti.xml index 1b20586..d103228 100644 --- a/bti.xml +++ b/bti.xml @@ -28,6 +28,8 @@ bti + + @@ -61,6 +63,27 @@ + + + + + Specify the action which you want to perform. Valid options + are "update" to send a message, "friends" to see your friends + timeline, "public" to track public timeline, "replies" to see + replies to your messages and "user" to see a specific user's + timeline. + + + + + + + + Specify the user you want to see his/her messages while the + action is "user". + + + @@ -191,6 +214,27 @@ + + + + + Specify the action which you want to perform. Valid options + are "update" to send a message, "friends" to see your friends + timeline, "public" to track public timeline, "replies" to see + replies to your messages and "user" to see a specific user's + timeline. + + + + + + + + Specify the user you want to see his/her messages while the + action is "user". + + + diff --git a/debian/changelog b/debian/changelog index 1992923..fd4b0bb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +bti (015-1) UNRELEASED; urgency=low + + * New upstream release; from the RELEASE-NOTES: + "--action support, bti now can output data from the twitter + and identica servers". + + -- gregor herrmann Thu, 12 Mar 2009 16:50:10 +0100 + bti (014-1) unstable; urgency=low * New upstream release.