]> git.sommitrealweird.co.uk Git - twirssi-net-twitter-lite.git/blobdiff - twirssi.pl
r652 - Patch from @mwalling - rename twitter_friend to twitter_follow
[twirssi-net-twitter-lite.git] / twirssi.pl
index cc7c05520c20dc847200048a0c79f28dfaf6562b..2f6bc8d4b2bdd551688da42cd6b6fe8332ae7134 100644 (file)
@@ -10,8 +10,8 @@ $Data::Dumper::Indent = 1;
 
 use vars qw($VERSION %IRSSI);
 
-$VERSION = "2.1.3beta";
-my ($REV) = '$Rev: 554 $' =~ /(\d+)/;
+$VERSION = "2.2.4beta";
+my ($REV) = '$Rev: 652 $' =~ /(\d+)/;
 %IRSSI = (
     authors     => 'Dan Boger',
     contact     => 'zigdon@gmail.com',
@@ -20,7 +20,7 @@ my ($REV) = '$Rev: 554 $' =~ /(\d+)/;
       . 'Can optionally set your bitlbee /away message to same',
     license => 'GNU GPL v2',
     url     => 'http://twirssi.com',
-    changed => '$Date: 2009-03-15 23:09:56 -0700 (Sun, 15 Mar 2009) $',
+    changed => '$Date: 2009-06-29 14:36:38 -0700 (Mon, 29 Jun 2009) $',
 );
 
 my $window;
@@ -30,11 +30,14 @@ my $user;
 my $defservice;
 my $poll;
 my $last_poll;
+my $last_friends_poll = 0;
 my %nicks;
 my %friends;
 my %tweet_cache;
 my %id_map;
-my $failwhale            = 0;
+my $failwhale  = 0;
+my $first_call = 1;
+
 my %irssi_to_mirc_colors = (
     '%k' => '01',
     '%r' => '05',
@@ -104,6 +107,105 @@ sub cmd_direct_as {
     }
 }
 
+sub cmd_retweet {
+    my ( $data, $server, $win ) = @_;
+
+    return unless &logged_in($twit);
+
+    $data =~ s/^\s+|\s+$//;
+    unless ($data) {
+        &notice("Usage: /retweet <nick[:num]> [comment]");
+        return;
+    }
+
+    my ( $id, $data ) = split ' ', $data, 2;
+
+    &cmd_retweet_as( "$user $id $data", $server, $win );
+}
+
+sub cmd_retweet_as {
+    my ( $data, $server, $win ) = @_;
+
+    unless ( Irssi::settings_get_bool("twirssi_track_replies") ) {
+        &notice("twirssi_track_replies is required in order to reteet.");
+        return;
+    }
+
+    return unless &logged_in($twit);
+
+    $data =~ s/^\s+|\s+$//;
+    my ( $username, $id, $data ) = split ' ', $data, 3;
+
+    unless ($username) {
+        &notice("Usage: /retweet_as <username> <nick[:num]> [comment]");
+        return;
+    }
+
+    return unless $username = &valid_username($username);
+
+    my $nick;
+    $id =~ s/[^\w\d\-:]+//g;
+    ( $nick, $id ) = split /:/, $id;
+    unless ( exists $id_map{ lc $nick } ) {
+        &notice("Can't find a tweet from $nick to retweet!");
+        return;
+    }
+
+    $id = $id_map{__indexes}{$nick} unless $id;
+    unless ( $id_map{ lc $nick }[$id] ) {
+        &notice("Can't find a tweet numbered $id from $nick to retweet!");
+        return;
+    }
+
+    unless ( $id_map{__tweets}{ lc $nick }[$id] ) {
+        &notice("The text of this tweet isn't saved, sorry!");
+        return;
+    }
+
+# Irssi::settings_add_str( "twirssi", "twirssi_retweet_format", 'RT $n: $t ${-- $c$}' );
+    my $text = Irssi::settings_get_str("twirssi_retweet_format");
+    $text =~ s/\$n/\@$nick/g;
+    if ($data) {
+        $text =~ s/\${|\$}//g;
+        $text =~ s/\$c/$data/;
+    } else {
+        $text =~ s/\${.*?\$}//;
+    }
+    $text =~ s/\$t/$id_map{__tweets}{ lc $nick }[$id]/;
+
+    $data = &shorten($text);
+
+    return if &too_long($data);
+
+    my $success = 1;
+    eval {
+        unless (
+            $twits{$username}->update(
+                {
+                    status                => $data,
+                    # in_reply_to_status_id => $id_map{ lc $nick }[$id]
+                }
+            )
+          )
+        {
+            &notice("Update failed");
+            $success = 0;
+        }
+    };
+    return unless $success;
+
+    if ($@) {
+        &notice("Update caused an error: $@.  Aborted");
+        return;
+    }
+
+    foreach ( $data =~ /@([-\w]+)/ ) {
+        $nicks{$1} = time;
+    }
+
+    &notice("Retweet sent");
+}
+
 sub cmd_tweet {
     my ( $data, $server, $win ) = @_;
 
@@ -173,10 +275,9 @@ sub cmd_reply {
         return;
     }
 
-    $data =~ s/^\s+|\s+$//;
     my ( $id, $data ) = split ' ', $data, 2;
     unless ( $id and $data ) {
-        &notice("Usage: /reply_as <nick[:num]> <update>");
+        &notice("Usage: /reply <nick[:num]> <update>");
         return;
     }
 
@@ -426,7 +527,7 @@ sub cmd_login {
                 &notice("Welcome to twirssi!"
                       . "  Perhaps you should add \@twirssi to your friends list,"
                       . " so you can be notified when a new version is release?"
-                      . "  Just type /twitter_friend twirssi." );
+                      . "  Just type /twitter_follow twirssi." );
             }
         }
         %nicks = %friends;
@@ -454,12 +555,12 @@ sub cmd_add_search {
         return;
     }
 
-    if ( exists $id_map{__searches}{$user}{$data} ) {
+    if ( exists $id_map{__searches}{"$user\@$defservice"}{$data} ) {
         &notice("Already had a subscription for '$data'");
         return;
     }
 
-    $id_map{__searches}{$user}{$data} = 1;
+    $id_map{__searches}{"$user\@$defservice"}{$data} = 1;
     &notice("Added subscription for '$data'");
 }
 
@@ -479,12 +580,12 @@ sub cmd_del_search {
         return;
     }
 
-    unless ( exists $id_map{__searches}{$user}{$data} ) {
+    unless ( exists $id_map{__searches}{"$user\@$defservice"}{$data} ) {
         &notice("No subscription found for '$data'");
         return;
     }
 
-    delete $id_map{__searches}{$user}{$data};
+    delete $id_map{__searches}{"$user\@$defservice"}{$data};
     &notice("Removed subscription for '$data'");
 }
 
@@ -667,66 +768,86 @@ sub get_updates {
         my $new_poll = time;
 
         my $error = 0;
+        my %context_cache;
         foreach ( keys %twits ) {
-            $error += &do_updates( $fh, $_, $twits{$_} );
+            $error++ unless &do_updates( $fh, $_, $twits{$_}, \%context_cache );
         }
 
-        my ( $added, $removed ) = &load_friends($fh);
-        if ( $added + $removed ) {
-            print $fh "type:debug %R***%n Friends list updated: ",
-              join( ", ",
-                sprintf( "%d added",   $added ),
-                sprintf( "%d removed", $removed ) ),
-              "\n";
-        }
         print $fh "__friends__\n";
+        if (
+            time - $last_friends_poll >
+            Irssi::settings_get_int('twitter_friends_poll') )
+        {
+            print $fh "__updated ", time, "\n";
+            my ( $added, $removed ) = &load_friends($fh);
+            if ( $added + $removed ) {
+                print $fh "type:debug %R***%n Friends list updated: ",
+                  join( ", ",
+                    sprintf( "%d added",   $added ),
+                    sprintf( "%d removed", $removed ) ),
+                  "\n";
+            }
+        }
+
         foreach ( sort keys %friends ) {
             print $fh "$_ $friends{$_}\n";
         }
 
         if ($error) {
             print $fh "type:debug Update encountered errors.  Aborted\n";
-            print $fh $last_poll;
+            print $fh "-- $last_poll";
         } else {
-            print $fh $new_poll;
+            print $fh "-- $new_poll";
         }
         close $fh;
         exit;
+    } else {
+        &ccrap("Failed to fork for updating: $!");
     }
     print scalar localtime, " - get_updates ends" if &debug;
 }
 
 sub do_updates {
-    my ( $fh, $username, $obj ) = @_;
+    my ( $fh, $username, $obj, $cache ) = @_;
 
     my $rate_limit = $obj->rate_limit_status();
     if ( $rate_limit and $rate_limit->{remaining_hits} < 1 ) {
         &notice("Rate limit exceeded for $username");
-        return 1;
+        return undef;
     }
 
     print scalar localtime, " - Polling for updates for $username" if &debug;
     my $tweets;
-    eval { $tweets = $obj->friends_timeline(); };
+    my $new_poll_id = 0;
+    eval {
+        if ( $id_map{__last_id}{$username}{timeline} )
+        {
+            $tweets = $obj->friends_timeline( { count => 100 } );
+        } else {
+            $tweets = $obj->friends_timeline();
+        }
+    };
 
     if ($@) {
-        print $fh
-          "type:debug Error during friends_timeline call: $@.  Aborted.\n";
-        return 1;
+        print $fh "type:debug Error during friends_timeline call: Aborted.\n";
+        print $fh "type:debug : $_\n" foreach split /\n/, Dumper($@);
+        return undef;
     }
 
     unless ( ref $tweets ) {
         if ( $obj->can("get_error") ) {
-            my $error;
+            my $error = "Unknown error";
             eval { $error = JSON::Any->jsonToObj( $obj->get_error() ) };
-            if ($@) { $error = $obj->get_error() }
-            print $fh "type:debug API Error during friends_timeline call: ",
-              "$error  Aborted.\n";
+            unless ($@) { $error = $obj->get_error() }
+            print $fh
+              "type:debug API Error during friends_timeline call: Aborted\n";
+            print $fh "type:debug : $_\n" foreach split /\n/, Dumper($error);
+
         } else {
             print $fh
               "type:debug API Error during friends_timeline call. Aborted.\n";
         }
-        return 1;
+        return undef;
     }
 
     foreach my $t ( reverse @$tweets ) {
@@ -740,53 +861,59 @@ sub do_updates {
         {
             $nicks{ $t->{in_reply_to_screen_name} } = time;
             my $context;
-            eval {
-                $context = $obj->show_status( $t->{in_reply_to_status_id} );
-            };
+            unless ( $cache->{ $t->{in_reply_to_status_id} } ) {
+                eval {
+                    $cache->{ $t->{in_reply_to_status_id} } =
+                      $obj->show_status( $t->{in_reply_to_status_id} );
+                };
+
+            }
+            $context = $cache->{ $t->{in_reply_to_status_id} };
 
             if ($context) {
                 my $ctext = decode_entities( $context->{text} );
                 $ctext = &hilight($ctext);
+                if ( $context->{truncated} and ref($obj) ne 'Net::Identica' ) {
+                    $ctext .=
+                        " -- http://twitter.com/$context->{user}{screen_name}"
+                      . "/status/$context->{id}";
+                }
                 printf $fh "id:%d account:%s nick:%s type:tweet %s\n",
                   $context->{id}, $username,
                   $context->{user}{screen_name}, $ctext;
-                if ( $context->{truncated} and ref($obj) ne 'Net::Identica' ) {
-                    printf $fh "id:%s account:%s nick:%s type:ellispis %s\n",
-                      $context->{id} . "-url", $username,
-                      $context->{user}{screen_name},
-"http://twitter.com/$context->{user}{screen_name}/status/$context->{id}";
-                }
                 $reply = "reply";
-            } elsif ($@) {
-                print $fh "type:debug request to get context failed: $@";
-            } else {
-                print $fh
-"type:debug Failed to get context from $t->{in_reply_to_screen_name}\n"
-                  if &debug;
             }
         }
         next
           if $t->{user}{screen_name} eq $username
               and not Irssi::settings_get_bool("show_own_tweets");
-        printf $fh "id:%d account:%s nick:%s type:%s %s\n",
-          $t->{id}, $username, $t->{user}{screen_name}, $reply, $text;
         if ( $t->{truncated} and ref($obj) ne 'Net::Identica' ) {
-            printf $fh "id:%s account:%s nick:%s type:ellispis %s\n",
-              $t->{id} . "-url", $username,
-              $t->{user}{screen_name},
-              "http://twitter.com/$t->{user}{screen_name}/status/$t->{id}";
+            $text .= " -- http://twitter.com/$t->{user}{screen_name}"
+              . "/status/$t->{id}";
         }
+        printf $fh "id:%d account:%s nick:%s type:%s %s\n",
+          $t->{id}, $username, $t->{user}{screen_name}, $reply, $text;
+        $new_poll_id = $t->{id} if $new_poll_id < $t->{id};
     }
+    printf $fh "id:%d account:%s type:last_id timeline\n",
+      $new_poll_id, $username;
 
     print scalar localtime, " - Polling for replies" if &debug;
+    $new_poll_id = 0;
     eval {
-        $tweets = $obj->replies( { since => HTTP::Date::time2str($last_poll) } )
-          || [];
+        if ( $id_map{__last_id}{$username}{reply} )
+        {
+            $tweets = $obj->replies(
+                { since_id => $id_map{__last_id}{$username}{reply} } )
+              || [];
+        } else {
+            $tweets = $obj->replies() || [];
+        }
     };
 
     if ($@) {
         print $fh "type:debug Error during replies call.  Aborted.\n";
-        return 1;
+        return undef;
     }
 
     foreach my $t ( reverse @$tweets ) {
@@ -795,26 +922,32 @@ sub do_updates {
 
         my $text = decode_entities( $t->{text} );
         $text = &hilight($text);
-        printf $fh "id:%d account:%s nick:%s type:tweet %s\n",
-          $t->{id}, $username, $t->{user}{screen_name}, $text;
         if ( $t->{truncated} ) {
-            printf $fh "id:%s account:%s nick:%s type:ellispis %s\n",
-              $t->{id} . "-url", $username,
-              $t->{user}{screen_name},
-              "http://twitter.com/$t->{user}{screen_name}/status/$t->{id}";
+            $text .= " -- http://twitter.com/$t->{user}{screen_name}"
+              . "/status/$t->{id}";
         }
+        printf $fh "id:%d account:%s nick:%s type:tweet %s\n",
+          $t->{id}, $username, $t->{user}{screen_name}, $text;
+        $new_poll_id = $t->{id} if $new_poll_id < $t->{id};
     }
+    printf $fh "id:%d account:%s type:last_id reply\n", $new_poll_id, $username;
 
     print scalar localtime, " - Polling for DMs" if &debug;
+    $new_poll_id = 0;
     eval {
-        $tweets =
-          $obj->direct_messages( { since => HTTP::Date::time2str($last_poll) } )
-          || [];
+        if ( $id_map{__last_id}{$username}{dm} )
+        {
+            $tweets = $obj->direct_messages(
+                { since_id => $id_map{__last_id}{$username}{dm} } )
+              || [];
+        } else {
+            $tweets = $obj->direct_messages() || [];
+        }
     };
 
     if ($@) {
         print $fh "type:debug Error during direct_messages call.  Aborted.\n";
-        return 1;
+        return undef;
     }
 
     foreach my $t ( reverse @$tweets ) {
@@ -822,7 +955,9 @@ sub do_updates {
         $text = &hilight($text);
         printf $fh "id:%d account:%s nick:%s type:dm %s\n",
           $t->{id}, $username, $t->{sender_screen_name}, $text;
+        $new_poll_id = $t->{id} if $new_poll_id < $t->{id};
     }
+    printf $fh "id:%d account:%s type:last_id dm\n", $new_poll_id, $username;
 
     print scalar localtime, " - Polling for subscriptions" if &debug;
     if ( $obj->can('search') and $id_map{__searches}{$username} ) {
@@ -842,14 +977,13 @@ sub do_updates {
             if ($@) {
                 print $fh
                   "type:debug Error during search($topic) call.  Aborted.\n";
-                return 1;
+                return undef;
             }
 
             unless ( $search->{max_id} ) {
-                print $fh
-"type:debug Invalid search results when searching for $topic.",
-                  "  Aborted.\n";
-                return 1;
+                print $fh "type:debug Invalid search results when searching",
+                  " for $topic. Aborted.\n";
+                return undef;
             }
 
             $id_map{__searches}{$username}{$topic} = $search->{max_id};
@@ -861,13 +995,16 @@ sub do_updates {
                 $text = &hilight($text);
                 printf $fh "id:%d account:%s nick:%s type:search topic:%s %s\n",
                   $t->{id}, $username, $t->{from_user}, $topic, $text;
+                $new_poll_id = $t->{id}
+                  if not $new_poll_id
+                      or $t->{id} < $new_poll_id;
             }
         }
     }
 
     print scalar localtime, " - Done" if &debug;
 
-    return 0;
+    return 1;
 }
 
 sub monitor_child {
@@ -877,28 +1014,31 @@ sub monitor_child {
 
     print scalar localtime, " - checking child log at $filename ($attempt)"
       if &debug;
-    my $new_last_poll;
+    my ($new_last_poll);
 
     # first time we run we don't want to print out *everything*, so we just
     # pretend
-    my $suppress = 0;
-    $suppress = 1 unless keys %tweet_cache;
 
     if ( open FILE, $filename ) {
         my @lines;
         my %new_cache;
         while (<FILE>) {
-            chomp;
             last if /^__friends__/;
+            unless (/\n$/) {    # skip partial lines
+                                # print "Skipping partial line: $_" if &debug;
+                next;
+            }
+            chomp;
             my $hilight = 0;
             my %meta;
+
             foreach my $key (qw/id account nick type topic/) {
                 if (s/^$key:(\S+)\s*//) {
                     $meta{$key} = $1;
                 }
             }
 
-            if ( not $meta{type} or $meta{type} ne 'searchid' ) {
+            if ( not $meta{type} or $meta{type} !~ /searchid|last_id/ ) {
                 if ( exists $meta{id} and exists $new_cache{ $meta{id} } ) {
                     next;
                 }
@@ -911,17 +1051,17 @@ sub monitor_child {
             }
 
             my $account = "";
-            if ( lc $meta{account} ne lc "$user\@$defservice" ) {
-                $meta{account} =~ s/\@(\w+)$//;
-                my $service = $1;
-                if (
-                    lc $service eq
-                    lc Irssi::settings_get_str("twirssi_default_service") )
-                {
-                    $account = "$meta{account}: ";
-                } else {
-                    $account = "$meta{account}\@$service: ";
-                }
+            $meta{account} =~ s/\@(\w+)$//;
+            $meta{service} = $1;
+            if (
+                lc $meta{service} eq
+                lc Irssi::settings_get_str("twirssi_default_service") )
+            {
+                $account = "$meta{account}: "
+                  if lc "$meta{account}\@$meta{service}" ne lc
+                      "$user\@$defservice";
+            } else {
+                $account = "$meta{account}\@$meta{service}: ";
             }
 
             my $marker = "";
@@ -931,14 +1071,16 @@ sub monitor_child {
                 and $meta{id} )
             {
                 $marker = ( $id_map{__indexes}{ $meta{nick} } + 1 ) % 100;
-                $id_map{ lc $meta{nick} }[$marker] = $meta{id};
-                $id_map{__indexes}{ $meta{nick} }  = $marker;
-                $marker                            = ":$marker";
+                $id_map{ lc $meta{nick} }[$marker]           = $meta{id};
+                $id_map{__indexes}{ $meta{nick} }            = $marker;
+                $id_map{__tweets}{ lc $meta{nick} }[$marker] = $_;
+                $marker                                      = ":$marker";
             }
 
             my $hilight_color =
               $irssi_to_mirc_colors{ Irssi::settings_get_str("hilight_color") };
-            if ( $_ eq '@'.substr($meta{account}, 0, index($meta{account}, "@"))
+            my $nick = "\@$meta{account}";
+            if ( $_ =~ /\Q$nick\E(?:\W|$)/i
                 and Irssi::settings_get_bool("twirssi_hilights") )
             {
                 $meta{nick} = "\cC$hilight_color$meta{nick}\cO";
@@ -951,9 +1093,6 @@ sub monitor_child {
                     ( MSGLEVEL_PUBLIC | $hilight ),
                     $meta{type}, $account, $meta{nick}, $marker, $_
                   ];
-            } elsif ( $meta{type} eq 'ellispis' ) {
-                push @lines,
-                  [ MSGLEVEL_PUBLIC, "tweet", $account, $meta{nick}, "", $_ ];
             } elsif ( $meta{type} eq 'search' ) {
                 push @lines,
                   [
@@ -978,8 +1117,9 @@ sub monitor_child {
             } elsif ( $meta{type} eq 'searchid' ) {
                 print "Search '$meta{topic}' returned id $meta{id}" if &debug;
                 if (
+                    not
                     exists $id_map{__searches}{ $meta{account} }{ $meta{topic} }
-                    and $meta{id} >=
+                    or $meta{id} >=
                     $id_map{__searches}{ $meta{account} }{ $meta{topic} } )
                 {
                     $id_map{__searches}{ $meta{account} }{ $meta{topic} } =
@@ -987,6 +1127,11 @@ sub monitor_child {
                 } elsif (&debug) {
                     print "Search '$meta{topic}' returned invalid id $meta{id}";
                 }
+            } elsif ( $meta{type} eq 'last_id' ) {
+                $id_map{__last_id}{"$meta{account}\@$meta{service}"}{$_} =
+                  $meta{id}
+                  if $id_map{__last_id}{"$meta{account}\@$meta{service}"}{$_} <
+                      $meta{id};
             } elsif ( $meta{type} eq 'error' ) {
                 push @lines, [ MSGLEVEL_MSGS, $_ ];
             } elsif ( $meta{type} eq 'debug' ) {
@@ -998,8 +1143,14 @@ sub monitor_child {
 
         %friends = ();
         while (<FILE>) {
-            if (/^\d+$/) {
-                $new_last_poll = $_;
+            if (/^__updated (\d+)$/) {
+                $last_friends_poll = $1;
+                print "Friend list updated" if &debug;
+                next;
+            }
+
+            if (/^-- (\d+)$/) {
+                ($new_last_poll) = ($1);
                 last;
             }
             my ( $f, $t ) = split ' ', $_;
@@ -1007,8 +1158,9 @@ sub monitor_child {
         }
 
         if ($new_last_poll) {
-            print "new last_poll = $new_last_poll" if &debug;
-            if ($suppress) {
+            print "new last_poll    = $new_last_poll" if &debug;
+            print "new last_poll_id = ", Dumper( $id_map{__last_id} ) if &debug;
+            if ($first_call) {
                 print "First call, not printing updates" if &debug;
             } else {
                 foreach my $line (@lines) {
@@ -1048,7 +1200,8 @@ sub monitor_child {
                     &ccrap("Failed to write replies to $file: $!");
                 }
             }
-            $failwhale = 0;
+            $failwhale  = 0;
+            $first_call = 0;
             return;
         }
     }
@@ -1088,7 +1241,10 @@ sub monitor_child {
             }
             $failwhale = 1;
         }
-        &ccrap("Haven't been able to get updated tweets since $since");
+
+        if ( time - $last_poll < 600 ) {
+            &ccrap("Haven't been able to get updated tweets since $since");
+        }
     }
 }
 
@@ -1166,7 +1322,7 @@ sub sig_complete {
     my ( $complist, $window, $word, $linestart, $want_space ) = @_;
 
     if (
-        $linestart =~ /^\/twitter_reply(?:_as)?\s*$/
+        $linestart =~ /^\/(?:retweet|twitter_reply)(?:_as)?\s*$/
         or ( Irssi::settings_get_bool("twirssi_use_reply_aliases")
             and $linestart =~ /^\/reply(?:_as)?\s*$/ )
       )
@@ -1178,6 +1334,13 @@ sub sig_complete {
           keys %{ $id_map{__indexes} };
     }
 
+    if ( $linestart =~ /^\/twitter_unfriend\s*$/ )
+    {    # /twitter_unfriend gets a nick
+        $word =~ s/^@//;
+        push @$complist, grep /^\Q$word/i,
+          sort { $nicks{$b} <=> $nicks{$a} } keys %nicks;
+    }
+
     # /tweet, /tweet_as, /dm, /dm_as - complete @nicks (and nicks as the first
     # arg to dm)
     if ( $linestart =~ /^\/(?:tweet|dm)/ ) {
@@ -1219,7 +1382,7 @@ sub hilight {
     if ( Irssi::settings_get_str("twirssi_topic_color") ) {
         my $c = Irssi::settings_get_str("twirssi_topic_color");
         $c = $irssi_to_mirc_colors{$c};
-        $text =~ s/(^|\W)\#([-\w]+)/$1\cC$c\#$2\cO/g if $c;
+        $text =~ s/(^|\W)(\#|\!)([-\w]+)/$1\cC$c$2$3\cO/g if $c;
     }
     $text =~ s/[\n\r]/ /g;
 
@@ -1316,10 +1479,15 @@ Irssi::settings_add_str( "twirssi", "twitter_passwords",       undef );
 Irssi::settings_add_str( "twirssi", "twirssi_default_service", "Twitter" );
 Irssi::settings_add_str( "twirssi", "twirssi_nick_color",      "%B" );
 Irssi::settings_add_str( "twirssi", "twirssi_topic_color",     "%r" );
+Irssi::settings_add_str( "twirssi", "twirssi_retweet_format",
+    'RT $n: "$t" ${-- $c$}' );
 Irssi::settings_add_str( "twirssi", "twirssi_location",
     ".irssi/scripts/twirssi.pl" );
 Irssi::settings_add_str( "twirssi", "twirssi_replies_store",
     ".irssi/scripts/twirssi.json" );
+
+Irssi::settings_add_int( "twirssi", "twitter_friends_poll", 600 );
+
 Irssi::settings_add_bool( "twirssi", "twirssi_upgrade_beta",      0 );
 Irssi::settings_add_bool( "twirssi", "tweet_to_away",             0 );
 Irssi::settings_add_bool( "twirssi", "show_reply_context",        0 );
@@ -1352,6 +1520,8 @@ if ($window) {
     Irssi::command_bind( "dm_as",                      "cmd_direct_as" );
     Irssi::command_bind( "tweet",                      "cmd_tweet" );
     Irssi::command_bind( "tweet_as",                   "cmd_tweet_as" );
+    Irssi::command_bind( "retweet",                    "cmd_retweet" );
+    Irssi::command_bind( "retweet_as",                 "cmd_retweet_as" );
     Irssi::command_bind( "twitter_reply",              "cmd_reply" );
     Irssi::command_bind( "twitter_reply_as",           "cmd_reply_as" );
     Irssi::command_bind( "twitter_login",              "cmd_login" );
@@ -1362,6 +1532,7 @@ if ($window) {
     Irssi::command_bind( "twitter_list_subscriptions", "cmd_list_search" );
     Irssi::command_bind( "twirssi_upgrade",            "cmd_upgrade" );
     Irssi::command_bind( "twitter_updates",            "get_updates" );
+    Irssi::command_bind( "bitlbee_away",               "update_away" );
     if ( Irssi::settings_get_bool("twirssi_use_reply_aliases") ) {
         Irssi::command_bind( "reply",    "cmd_reply" );
         Irssi::command_bind( "reply_as", "cmd_reply_as" );
@@ -1394,18 +1565,18 @@ if ($window) {
         }
     );
     Irssi::command_bind(
-        "twitter_friend",
+        "twitter_follow",
         &gen_cmd(
-            "/twitter_friend <username>",
+            "/twitter_follow <username>",
             "create_friend",
             sub { &notice("Following $_[0]"); $nicks{ $_[0] } = time; }
         )
     );
     Irssi::command_bind(
-        "twitter_unfriend",
+        "twitter_unfollow",
         &gen_cmd(
             "/twitter_unfriend <username>",
-            "destroy_friend",
+            "destroy_follow",
             sub { &notice("Stopped following $_[0]"); delete $nicks{ $_[0] }; }
         )
     );