Add support for api page numbers in read mode
[gregoa/bti.git] / bti.c
1 /*
2  * Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com>
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the
6  * Free Software Foundation version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16  */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <stddef.h>
21 #include <string.h>
22 #include <getopt.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <time.h>
28 #include <sys/stat.h>
29 #include <curl/curl.h>
30 #include <readline/readline.h>
31 #include <libxml/xmlmemory.h>
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include "bti_version.h"
35
36
37 #define zalloc(size)    calloc(size, 1)
38
39 #define dbg(format, arg...)                                             \
40         do {                                                            \
41                 if (debug)                                              \
42                         printf("%s: " format , __func__ , ## arg);      \
43         } while (0)
44
45
46 static int debug;
47
48 enum host {
49         HOST_TWITTER = 0,
50         HOST_IDENTICA = 1,
51 };
52
53 enum action {
54         ACTION_UPDATE = 0,
55         ACTION_FRIENDS = 1,
56         ACTION_USER = 2,
57         ACTION_REPLIES = 4,
58         ACTION_PUBLIC = 8,
59         ACTION_UNKNOWN = 16
60 };
61
62 struct session {
63         char *password;
64         char *account;
65         char *tweet;
66         char *proxy;
67         char *time;
68         char *homedir;
69         char *logfile;
70         char *user;
71         int bash;
72         int page;
73         enum host host;
74         enum action action;
75 };
76
77 struct bti_curl_buffer {
78         char *data;
79         enum action action;
80         int length;
81 };
82
83 static void display_help(void)
84 {
85         fprintf(stdout, "bti - send tweet to twitter or identi.ca\n");
86         fprintf(stdout, "Version: " BTI_VERSION "\n");
87         fprintf(stdout, "Usage:\n");
88         fprintf(stdout, "  bti [options]\n");
89         fprintf(stdout, "options are:\n");
90         fprintf(stdout, "  --account accountname\n");
91         fprintf(stdout, "  --password password\n");
92         fprintf(stdout, "  --action action\n");
93         fprintf(stdout, "    ('update', 'friends', 'public', 'replies' or 'user')\n");
94         fprintf(stdout, "  --user screenname\n");
95         fprintf(stdout, "  --proxy PROXY:PORT\n");
96         fprintf(stdout, "  --host HOST\n");
97         fprintf(stdout, "  --logfile logfile\n");
98         fprintf(stdout, "  --bash\n");
99         fprintf(stdout, "  --debug\n");
100         fprintf(stdout, "  --version\n");
101         fprintf(stdout, "  --help\n");
102 }
103
104 static void display_version(void)
105 {
106         fprintf(stdout, "bti - version %s\n", BTI_VERSION);
107 }
108
109 static struct session *session_alloc(void)
110 {
111         struct session *session;
112
113         session = zalloc(sizeof(*session));
114         if (!session)
115                 return NULL;
116         return session;
117 }
118
119 static void session_free(struct session *session)
120 {
121         if (!session)
122                 return;
123         free(session->password);
124         free(session->account);
125         free(session->tweet);
126         free(session->proxy);
127         free(session->time);
128         free(session->homedir);
129         free(session->user);
130         free(session);
131 }
132
133 static struct bti_curl_buffer *bti_curl_buffer_alloc(enum action action)
134 {
135         struct bti_curl_buffer *buffer;
136
137         buffer = zalloc(sizeof(*buffer));
138         if (!buffer)
139                 return NULL;
140
141         /* start out with a data buffer of 1 byte to
142          * make the buffer fill logic simpler */
143         buffer->data = zalloc(1);
144         if (!buffer->data) {
145                 free(buffer);
146                 return NULL;
147         }
148         buffer->length = 0;
149         buffer->action = action;
150         return buffer;
151 }
152
153 static void bti_curl_buffer_free(struct bti_curl_buffer *buffer)
154 {
155         if (!buffer)
156                 return;
157         free(buffer->data);
158         free(buffer);
159 }
160
161 static const char *twitter_user_url    = "http://twitter.com/statuses/user_timeline/";
162 static const char *twitter_update_url  = "https://twitter.com/statuses/update.xml";
163 static const char *twitter_public_url  = "http://twitter.com/statuses/public_timeline.xml";
164 static const char *twitter_friends_url = "https://twitter.com/statuses/friends_timeline.xml";
165 static const char *twitter_replies_url = "http://twitter.com/statuses/replies.xml";
166
167 static const char *identica_user_url    = "http://identi.ca/api/statuses/user_timeline/";
168 static const char *identica_update_url  = "http://identi.ca/api/statuses/update.xml";
169 static const char *identica_public_url  = "http://identi.ca/api/statuses/public_timeline.xml";
170 static const char *identica_friends_url = "http://identi.ca/api/statuses/friends_timeline.xml";
171 static const char *identica_replies_url = "http://identi.ca/api/statuses/replies.xml";
172
173 static CURL *curl_init(void)
174 {
175         CURL *curl;
176
177         curl = curl_easy_init();
178         if (!curl) {
179                 fprintf(stderr, "Can not init CURL!\n");
180                 return NULL;
181         }
182         /* some ssl sanity checks on the connection we are making */
183         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
184         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
185         return curl;
186 }
187
188 void parse_statuses(xmlDocPtr doc, xmlNodePtr current)
189 {
190         xmlChar *text = NULL;
191         xmlChar *user = NULL;
192         xmlNodePtr userinfo;
193
194         current = current->xmlChildrenNode;
195         while (current != NULL) {
196                 if (current->type == XML_ELEMENT_NODE) {
197                         if (!xmlStrcmp(current->name, (const xmlChar *)"text"))
198                                 text = xmlNodeListGetString(doc, current->xmlChildrenNode, 1);
199                         if (!xmlStrcmp(current->name, (const xmlChar *)"user")) {
200                                 userinfo = current->xmlChildrenNode;
201                                 while (userinfo != NULL) {
202                                         if ((!xmlStrcmp(userinfo->name, (const xmlChar *)"screen_name"))) {
203                                                 if (user)
204                                                         xmlFree(user);
205                                                 user = xmlNodeListGetString(doc, userinfo->xmlChildrenNode, 1);
206                                         }
207                                         userinfo = userinfo->next;
208                                 }
209                         }
210                         if (user && text) {
211                                 printf("[%s] %s\n", user, text);
212                                 xmlFree(user);
213                                 xmlFree(text);
214                                 user = NULL;
215                                 text = NULL;
216                         }
217                 }
218                 current = current->next;
219         }
220
221         return;
222 }
223
224 static void parse_timeline(char *document)
225 {
226         xmlDocPtr doc;
227         xmlNodePtr current;
228         doc = xmlReadMemory(document, strlen(document), "timeline.xml", NULL, XML_PARSE_NOERROR);
229
230         if (doc == NULL)
231                 return;
232
233         current = xmlDocGetRootElement(doc);
234         if (current == NULL) {
235                 fprintf(stderr, "empty document\n");
236                 xmlFreeDoc(doc);
237                 return;
238         }
239
240         if (xmlStrcmp(current->name, (const xmlChar *) "statuses")) {
241                 fprintf(stderr, "unexpected document type\n");
242                 xmlFreeDoc(doc);
243                 return;
244         }
245
246         current = current->xmlChildrenNode;
247         while (current != NULL) {
248                 if ((!xmlStrcmp(current->name, (const xmlChar *)"status")))
249                         parse_statuses(doc, current);
250                 current = current->next;
251         }
252         xmlFreeDoc(doc);
253
254         return;
255 }
256
257 size_t curl_callback(void *buffer, size_t size, size_t nmemb, void *userp)
258 {
259         struct bti_curl_buffer *curl_buf = userp;
260         size_t buffer_size = size * nmemb;
261         char *temp;
262
263         if ((!buffer) || (!buffer_size) || (!curl_buf))
264                 return -EINVAL;
265
266         /* add to the data we already have */
267         temp = zalloc(curl_buf->length + buffer_size + 1);
268         if (!temp)
269                 return -ENOMEM;
270
271         memcpy(temp, curl_buf->data, curl_buf->length);
272         free(curl_buf->data);
273         curl_buf->data = temp;
274         memcpy(&curl_buf->data[curl_buf->length], (char *)buffer, buffer_size);
275         curl_buf->length += buffer_size;
276         if (curl_buf->action)
277                 parse_timeline(curl_buf->data);
278
279         dbg("%s\n", curl_buf->data);
280
281         return buffer_size;
282 }
283
284 static int send_request(struct session *session)
285 {
286         char user_password[500];
287         char data[500];
288         /* is there usernames longer than 22 chars? */
289         char user_url[70];
290         struct bti_curl_buffer *curl_buf;
291         CURL *curl = NULL;
292         CURLcode res;
293         struct curl_httppost *formpost = NULL;
294         struct curl_httppost *lastptr = NULL;
295         struct curl_slist *slist = NULL;
296
297         if (!session)
298                 return -EINVAL;
299
300         curl_buf = bti_curl_buffer_alloc(session->action);
301         if (!curl_buf)
302                 return -ENOMEM;
303
304         curl = curl_init();
305         if (!curl)
306                 return -EINVAL;
307
308         switch (session->action) {
309         case ACTION_UPDATE:
310                 snprintf(user_password, sizeof(user_password), "%s:%s",
311                          session->account, session->password);
312                 snprintf(data, sizeof(data), "status=\"%s\"", session->tweet);
313                 curl_formadd(&formpost, &lastptr,
314                              CURLFORM_COPYNAME, "status",
315                              CURLFORM_COPYCONTENTS, session->tweet,
316                              CURLFORM_END);
317
318                 curl_formadd(&formpost, &lastptr,
319                              CURLFORM_COPYNAME, "source",
320                              CURLFORM_COPYCONTENTS, "bti",
321                              CURLFORM_END);
322
323                 curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
324                 slist = curl_slist_append(slist, "Expect:");
325                 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
326                 switch (session->host) {
327                 case HOST_TWITTER:
328                         curl_easy_setopt(curl, CURLOPT_URL, twitter_update_url);
329                         break;
330                 case HOST_IDENTICA:
331                         curl_easy_setopt(curl, CURLOPT_URL, identica_update_url);
332                         break;
333                 }
334                 curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
335
336                 break;
337         case ACTION_FRIENDS:
338                 snprintf(user_password, sizeof(user_password), "%s:%s",
339                          session->account, session->password);
340                 switch (session->host) {
341                 case HOST_TWITTER:
342                         sprintf(user_url, "%s?page=%d", twitter_friends_url, session->page);
343                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
344                         break;
345                 case HOST_IDENTICA:
346                         sprintf(user_url, "%s?page=%d", identica_friends_url, session->page);
347                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
348                         break;
349                 }
350                 curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
351
352                 break;
353         case ACTION_USER:
354                 switch (session->host) {
355                 case HOST_TWITTER:
356                         sprintf(user_url, "%s%s.xml?page=%d", twitter_user_url, session->user, session->page);
357                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
358                         break;
359                 case HOST_IDENTICA:
360                         sprintf(user_url, "%s%s.xml?page=%d", identica_user_url, session->user, session->page);
361                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
362                         break;
363                 }
364
365                 break;
366         case ACTION_REPLIES:
367                 snprintf(user_password, sizeof(user_password), "%s:%s",
368                          session->account, session->password);
369                 switch (session->host) {
370                 case HOST_TWITTER:
371                         sprintf(user_url, "%s?page=%d", twitter_replies_url, session->page);
372                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
373                         break;
374                 case HOST_IDENTICA:
375                         sprintf(user_url, "%s?page=%d", identica_replies_url, session->page);
376                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
377                         break;
378                 }
379                 curl_easy_setopt(curl, CURLOPT_USERPWD, user_password);
380
381                 break;
382         case ACTION_PUBLIC:
383                 switch (session->host) {
384                 case HOST_TWITTER:
385                         sprintf(user_url, "%s?page=%d", twitter_public_url, session->page);
386                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
387                         break;
388                 case HOST_IDENTICA:
389                         sprintf(user_url, "%s?page=%d", identica_public_url, session->page);
390                         curl_easy_setopt(curl, CURLOPT_URL, user_url);
391                         break;
392                 }
393
394                 break;
395         default:
396                 break;
397         }
398
399         if (session->proxy)
400                 curl_easy_setopt(curl, CURLOPT_PROXY, session->proxy);
401
402         if (debug)
403                 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
404
405         dbg("user_password = %s\n", user_password);
406         dbg("data = %s\n", data);
407         dbg("proxy = %s\n", session->proxy);
408
409         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback);
410         curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf);
411         res = curl_easy_perform(curl);
412         if (res && !session->bash) {
413                 fprintf(stderr, "error(%d) trying to perform operation\n", res);
414                 return -EINVAL;
415         }
416
417         curl_easy_cleanup(curl);
418         if (session->action == ACTION_UPDATE)
419                 curl_formfree(formpost);
420         bti_curl_buffer_free(curl_buf);
421         return 0;
422 }
423
424 static void parse_configfile(struct session *session)
425 {
426         FILE *config_file;
427         char *line = NULL;
428         size_t len = 0;
429         char *account = NULL;
430         char *password = NULL;
431         char *host = NULL;
432         char *proxy = NULL;
433         char *logfile = NULL;
434         char *action = NULL;
435         char *user = NULL;
436         char *file;
437
438         /* config file is ~/.bti  */
439         file = alloca(strlen(session->homedir) + 7);
440
441         sprintf(file, "%s/.bti", session->homedir);
442
443         config_file = fopen(file, "r");
444
445         /* No error if file does not exist or is unreadable.  */
446         if (config_file == NULL)
447                 return;
448
449         do {
450                 ssize_t n = getline(&line, &len, config_file);
451                 if (n < 0)
452                         break;
453                 if (line[n - 1] == '\n')
454                         line[n - 1] = '\0';
455                 /* Parse file.  Format is the usual value pairs:
456                    account=name
457                    passwort=value
458                    # is a comment character
459                 */
460                 *strchrnul(line, '#') = '\0';
461                 char *c = line;
462                 while (isspace(*c))
463                         c++;
464                 /* Ignore blank lines.  */
465                 if (c[0] == '\0')
466                         continue;
467
468                 if (!strncasecmp(c, "account", 7) && (c[7] == '=')) {
469                         c += 8;
470                         if (c[0] != '\0')
471                                 account = strdup(c);
472                 } else if (!strncasecmp(c, "password", 8) &&
473                            (c[8] == '=')) {
474                         c += 9;
475                         if (c[0] != '\0')
476                                 password = strdup(c);
477                 } else if (!strncasecmp(c, "host", 4) &&
478                            (c[4] == '=')) {
479                         c += 5;
480                         if (c[0] != '\0')
481                                 host = strdup(c);
482                 } else if (!strncasecmp(c, "proxy", 5) &&
483                            (c[5] == '=')) {
484                         c += 6;
485                         if (c[0] != '\0')
486                                 proxy = strdup(c);
487                 } else if (!strncasecmp(c, "logfile", 7) &&
488                            (c[7] == '=')) {
489                         c += 8;
490                         if (c[0] != '\0')
491                                 logfile = strdup(c);
492                 } else if (!strncasecmp(c, "action", 6) &&
493                            (c[6] == '=')) {
494                         c += 7;
495                         if (c[0] != '\0')
496                                 action = strdup(c);
497                 } else if (!strncasecmp(c, "user", 4) &&
498                                 (c[4] == '=')) {
499                         c += 5;
500                         if (c[0] != '\0')
501                                 user = strdup(c);
502                 }
503         } while (!feof(config_file));
504
505         if (password)
506                 session->password = password;
507         if (account)
508                 session->account = account;
509         if (host) {
510                 if (strcasecmp(host, "twitter") == 0)
511                         session->host = HOST_TWITTER;
512                 if (strcasecmp(host, "identica") == 0)
513                         session->host = HOST_IDENTICA;
514                 free(host);
515         }
516         if (proxy) {
517                 if (session->proxy)
518                         free(session->proxy);
519                 session->proxy = proxy;
520         }
521         if (logfile)
522                 session->logfile = logfile;
523         if (action) {
524                 if (strcasecmp(action, "update") == 0)
525                         session->action = ACTION_UPDATE;
526                 else if (strcasecmp(action, "friends") == 0)
527                         session->action = ACTION_FRIENDS;
528                 else if (strcasecmp(action, "user") == 0)
529                         session->action = ACTION_USER;
530                 else if (strcasecmp(action, "replies") == 0)
531                         session->action = ACTION_REPLIES;
532                 else if (strcasecmp(action, "public") == 0)
533                         session->action = ACTION_PUBLIC;
534                 else
535                         session->action = ACTION_UNKNOWN;
536                 free(action);
537         }
538         if (user) {
539                 session->user = user;
540         }
541
542         /* Free buffer and close file.  */
543         free(line);
544         fclose(config_file);
545 }
546
547 static void log_session(struct session *session, int retval)
548 {
549         FILE *log_file;
550         char *filename;
551         char *host;
552
553         /* Only log something if we have a log file set */
554         if (!session->logfile)
555                 return;
556
557         filename = alloca(strlen(session->homedir) +
558                           strlen(session->logfile) + 3);
559
560         sprintf(filename, "%s/%s", session->homedir, session->logfile);
561
562         log_file = fopen(filename, "a+");
563         if (log_file == NULL)
564                 return;
565         switch (session->host) {
566         case HOST_TWITTER:
567                 host = "twitter";
568                 break;
569         case HOST_IDENTICA:
570                 host = "identi.ca";
571                 break;
572         default:
573                 host = "unknown";
574                 break;
575         }
576
577         switch (session->action) {
578         case ACTION_UPDATE:
579                 if (retval)
580                         fprintf(log_file, "%s: host=%s tweet failed\n",
581                                 session->time, host);
582                 else
583                         fprintf(log_file, "%s: host=%s tweet=%s\n",
584                                 session->time, host, session->tweet);
585                 break;
586         case ACTION_FRIENDS:
587                 fprintf(log_file, "%s: host=%s retrieving friends timeline\n",
588                         session->time, host);
589                 break;
590         case ACTION_USER:
591                 fprintf(log_file, "%s: host=%s retrieving %s's timeline\n",
592                         session->time, host, session->user);
593                 break;
594         case ACTION_REPLIES:
595                 fprintf(log_file, "%s: host=%s retrieving replies\n",
596                         session->time, host);
597                 break;
598         case ACTION_PUBLIC:
599                 fprintf(log_file, "%s: host=%s retrieving public timeline\n",
600                         session->time, host);
601                 break;
602         default:
603                 break;
604         }
605
606         fclose(log_file);
607 }
608
609 static char *get_string_from_stdin(void)
610 {
611         char *temp;
612         char *string;
613
614         string = zalloc(1000);
615         if (!string)
616                 return NULL;
617
618         if (!fgets(string, 999, stdin))
619                 return NULL;
620         temp = strchr(string, '\n');
621         *temp = '\0';
622         return string;
623 }
624
625 int main(int argc, char *argv[], char *envp[])
626 {
627         static const struct option options[] = {
628                 { "debug", 0, NULL, 'd' },
629                 { "account", 1, NULL, 'a' },
630                 { "password", 1, NULL, 'p' },
631                 { "host", 1, NULL, 'H' },
632                 { "proxy", 1, NULL, 'P' },
633                 { "action", 1, NULL, 'A' },
634                 { "user", 1, NULL, 'u' },
635                 { "logfile", 1, NULL, 'L' },
636                 { "help", 0, NULL, 'h' },
637                 { "bash", 0, NULL, 'b' },
638                 { "page", 1, NULL, 'g' },
639                 { "version", 0, NULL, 'v' },
640                 { }
641         };
642         struct session *session;
643         pid_t child;
644         char *tweet;
645         int retval = 0;
646         int option;
647         char *http_proxy;
648         time_t t;
649         int page_nr;
650
651         debug = 0;
652         rl_bind_key('\t', rl_insert);
653
654         session = session_alloc();
655         if (!session) {
656                 fprintf(stderr, "no more memory...\n");
657                 return -1;
658         }
659
660         /* get the current time so that we can log it later */
661         time(&t);
662         session->time = strdup(ctime(&t));
663         session->time[strlen(session->time)-1] = 0x00;
664
665         session->homedir = strdup(getenv("HOME"));
666
667         curl_global_init(CURL_GLOBAL_ALL);
668
669         /* Set environment variables first, before reading command line options
670          * or config file values. */
671         http_proxy = getenv("http_proxy");
672         if (http_proxy) {
673                 if (session->proxy)
674                         free(session->proxy);
675                 session->proxy = strdup(http_proxy);
676                 dbg("http_proxy = %s\n", session->proxy);
677         }
678
679         parse_configfile(session);
680
681         while (1) {
682                 option = getopt_long_only(argc, argv, "dqe:p:P:H:a:A:u:hg:",
683                                           options, NULL);
684                 if (option == -1)
685                         break;
686                 switch (option) {
687                 case 'd':
688                         debug = 1;
689                         break;
690                 case 'a':
691                         if (session->account)
692                                 free(session->account);
693                         session->account = strdup(optarg);
694                         dbg("account = %s\n", session->account);
695                         break;
696                 case 'g':
697                         page_nr = atoi(optarg);
698                         dbg("page = %d\n", page_nr);
699                         session->page = page_nr;
700                         break;
701                 case 'p':
702                         if (session->password)
703                                 free(session->password);
704                         session->password = strdup(optarg);
705                         dbg("password = %s\n", session->password);
706                         break;
707                 case 'P':
708                         if (session->proxy)
709                                 free(session->proxy);
710                         session->proxy = strdup(optarg);
711                         dbg("proxy = %s\n", session->proxy);
712                         break;
713                 case 'A':
714                         if (strcasecmp(optarg, "update") == 0)
715                                 session->action = ACTION_UPDATE;
716                         else if (strcasecmp(optarg, "friends") == 0)
717                                 session->action = ACTION_FRIENDS;
718                         else if (strcasecmp(optarg, "user") == 0)
719                                 session->action = ACTION_USER;
720                         else if (strcasecmp(optarg, "replies") == 0)
721                                 session->action = ACTION_REPLIES;
722                         else if (strcasecmp(optarg, "public") == 0)
723                                 session->action = ACTION_PUBLIC;
724                         else
725                                 session->action = ACTION_UNKNOWN;
726                         dbg("action = %d\n", session->action);
727                         break;
728                 case 'u':
729                         if (session->user)
730                                 free(session->user);
731                         session->user = strdup(optarg);
732                         dbg("user = %s\n", session->user);
733                         break;
734                 case 'L':
735                         if (session->logfile)
736                                 free(session->logfile);
737                         session->logfile = strdup(optarg);
738                         dbg("logfile = %s\n", session->logfile);
739                         break;
740                 case 'H':
741                         if (strcasecmp(optarg, "twitter") == 0)
742                                 session->host = HOST_TWITTER;
743                         if (strcasecmp(optarg, "identica") == 0)
744                                 session->host = HOST_IDENTICA;
745                         dbg("host = %d\n", session->host);
746                         break;
747                 case 'b':
748                         session->bash = 1;
749                         break;
750                 case 'h':
751                         display_help();
752                         goto exit;
753                 case 'v':
754                         display_version();
755                         goto exit;
756                 default:
757                         display_help();
758                         goto exit;
759                 }
760         }
761
762         if (session->action == ACTION_UNKNOWN) {
763                 fprintf(stderr, "Unknown action, valid actions are:\n");
764                 fprintf(stderr, "'update', 'friends', 'public', 'replies' or 'user'.\n");
765                 goto exit;
766         }
767
768         if (!session->account) {
769                 fprintf(stdout, "Enter twitter account: ");
770                 session->account = readline(NULL);
771         }
772
773         if (!session->password) {
774                 fprintf(stdout, "Enter twitter password: ");
775                 session->password = readline(NULL);
776         }
777
778         if (session->action == ACTION_UPDATE) {
779                 if (session->bash)
780                         tweet = get_string_from_stdin();
781                 else
782                         tweet = readline("tweet: ");
783                 if (!tweet || strlen(tweet) == 0) {
784                         dbg("no tweet?\n");
785                         return -1;
786                 }
787
788                 session->tweet = zalloc(strlen(tweet) + 10);
789                 if (session->bash)
790                         sprintf(session->tweet, "$ %s", tweet);
791                 else
792                         sprintf(session->tweet, "%s", tweet);
793
794                 free(tweet);
795                 dbg("tweet = %s\n", session->tweet);
796         }
797
798         if (!session->user)
799                 session->user = strdup(session->account);
800
801         if (session->page == 0)
802                 session->page = 1;
803         dbg("account = %s\n", session->account);
804         dbg("password = %s\n", session->password);
805         dbg("host = %d\n", session->host);
806         dbg("action = %d\n", session->action);
807
808         /* fork ourself so that the main shell can get on
809          * with it's life as we try to connect and handle everything
810          */
811         if (session->bash) {
812                 child = fork();
813                 if (child) {
814                         dbg("child is %d\n", child);
815                         exit(0);
816                 }
817         }
818
819         retval = send_request(session);
820         if (retval && !session->bash)
821                 fprintf(stderr, "operation failed\n");
822
823         log_session(session, retval);
824 exit:
825         session_free(session);
826         return retval;;
827 }