[twirssi] Upgrade to 2.6.0

Julian C. Dunn jdunn at fedoraproject.org
Sat Jun 15 03:42:22 UTC 2013


commit c5dc33c2c5025b840a88831cc8d1f2e51603d5bf
Author: Julian C. Dunn <jdunn at aquezada.com>
Date:   Fri Jun 14 23:42:07 2013 -0400

    Upgrade to 2.6.0

 twirssi-2.5.1.pl => twirssi-2.6.0.pl |  588 +++++++++++++++++++++-------------
 1 files changed, 357 insertions(+), 231 deletions(-)
---
diff --git a/twirssi-2.5.1.pl b/twirssi-2.6.0.pl
similarity index 88%
rename from twirssi-2.5.1.pl
rename to twirssi-2.6.0.pl
index dfc4b90..4d97a9d 100644
--- a/twirssi-2.5.1.pl
+++ b/twirssi-2.6.0.pl
@@ -17,16 +17,16 @@ $Data::Dumper::Indent = 1;
 
 use vars qw($VERSION %IRSSI);
 
-$VERSION = sprintf '%s', q$Version: v2.5.1$ =~ /^\w+:\s+v(\S+)/;
+$VERSION = sprintf '%s', q$Version: v2.6.0$ =~ /^\w+:\s+v(\S+)/;
 %IRSSI   = (
-    authors     => 'Dan Boger',
+    authors     => '@zigdon, @gedge',
     contact     => 'zigdon at gmail.com',
     name        => 'twirssi',
     description => 'Send twitter updates using /tweet.  '
       . 'Can optionally set your bitlbee /away message to same',
     license => 'GNU GPL v2',
     url     => 'http://twirssi.com',
-    changed => '$Date: 2012-02-07 22:34:25 +0000$',
+    changed => '$Date: 2013-06-08 13:30:00 +0000$',
 );
 
 my $twit;	# $twit is current logged-in Net::Twitter object (usually one of %twits)
@@ -92,6 +92,7 @@ my @settings_defn = (
         [ 'poll_store',        'twirssi_poll_store',        's', Irssi::get_irssi_dir . "/scripts/$IRSSI{name}.polls" ],
         [ 'id_store',          'twirssi_id_store',          's', Irssi::get_irssi_dir . "/scripts/$IRSSI{name}.ids" ],
         [ 'retweet_format',    'twirssi_retweet_format',    's', 'RT $n: "$t" ${-- $c$}' ],
+        [ 'retweeted_format',  'twirssi_retweeted_format',  's', 'RT $n: $t' ],
         [ 'stripped_tags',     'twirssi_stripped_tags',     's', '',			'list{,}' ],
         [ 'topic_color',       'twirssi_topic_color',       's', '%r', ],
         [ 'timestamp_format',  'twirssi_timestamp_format',  's', '%H:%M:%S', ],
@@ -109,8 +110,10 @@ my @settings_defn = (
         [ 'url_args',          'short_url_args',            's', undef ],
         [ 'window',            'twitter_window',            's', 'twitter' ],
         [ 'debug_win_name',    'twirssi_debug_win_name',    's', '' ],
+        [ 'limit_user_tweets', 'twitter_user_results',      's', '20' ],
 
         [ 'always_shorten',    'twirssi_always_shorten',    'b', 0 ],
+        [ 'rt_to_expand',      'twirssi_retweet_to_expand', 'b', 1 ],
         [ 'avoid_ssl',         'twirssi_avoid_ssl',         'b', 0 ],
         [ 'debug',             'twirssi_debug',             'b', 0 ],
         [ 'notify_timeouts',   'twirssi_notify_timeouts',   'b', 1 ],
@@ -195,7 +198,7 @@ sub cmd_direct_as {
 
     eval {
         if ( $twits{$username}
-            ->new_direct_message( { user => $target, text => $text } ) ) {
+            ->new_direct_message( { screen_name => $target, text => $text } ) ) {
             &notice( [ "dm", $target_norm ], "DM sent to $target: $text" );
             $nicks{$target} = time;
         } else {
@@ -254,7 +257,7 @@ sub cmd_retweet_as {
         return;
     }
 
-    $id = $state{__indexes}{lc $nick} unless $id;
+    $id = $state{__indexes}{lc $nick} unless defined $id;
     unless ( $state{__ids}{ lc $nick }[$id] ) {
         &notice( [ "tweet", $username ],
             "Can't find a tweet numbered $id from $nick to retweet!" );
@@ -267,16 +270,8 @@ sub cmd_retweet_as {
         return;
     }
 
-# Irssi::settings_add_str( $IRSSI{name}, "twirssi_retweet_format", 'RT $n: $t ${-- $c$}' );
-    my $text = $settings{retweet_format};
-    $text =~ s/\$n/\@$nick/g;
-    if ($data) {
-        $text =~ s/\${|\$}//g;
-        $text =~ s/\$c/$data/;
-    } else {
-        $text =~ s/\${.*?\$}//;
-    }
-    $text =~ s/\$t/$state{__tweets}{ lc $nick }[$id]/;
+    my $text = &format_expand(fmt => $settings{retweet_format}, nick => $nick, data => $data,
+                 tweet => $state{__tweets}{ lc $nick }[$id]);
 
     my $modified = $data;
     $data = &shorten($text);
@@ -323,6 +318,20 @@ sub cmd_retweet_as {
 }
 
 
+sub format_expand {
+    my %args = @_;
+    $args{fmt} =~ s/\$n/\@$args{nick}/g;
+    if (defined $args{data} and $args{data} ne '') {
+        $args{fmt} =~ s/\${|\$}//g;
+        $args{fmt} =~ s/\$c/$args{data}/g;
+    } else {
+        $args{fmt} =~ s/\${.*?\$}//g;
+    }
+    $args{fmt} =~ s/\$t/$args{tweet}/g;
+    return $args{fmt};
+}
+
+
 sub cmd_retweet_to_window {
     my ( $data, $server, $win ) = @_;
 
@@ -337,7 +346,7 @@ sub cmd_retweet_to_window {
         return;
     }
 
-    $id = $state{__indexes}{lc $nick} unless $id;
+    $id = $state{__indexes}{lc $nick} unless defined $id;
     unless ( $state{__ids}{ lc $nick }[$id] ) {
         &notice( [ "tweet" ],
             "Can't find a tweet numbered $id from $nick to retweet!" );
@@ -371,16 +380,8 @@ sub cmd_retweet_to_window {
         return;
     }
 
-    my $text = $settings{retweet_format};
-    $text =~ s/\$n/\@$nick/g;
-    if ($data) {
-        $text =~ s/\${|\$}//g;
-        $text =~ s/\$c/$data/;
-    } else {
-        $text =~ s/\${.*?\$}//;
-    }
-    my $tweet = &unshorten($state{__tweets}{ lc $nick }[$id]);
-    $text =~ s/\$t/$tweet/;
+    my $text = &format_expand(fmt => $settings{retweet_format}, nick => $nick, data => $data,
+                 tweet => &post_process_tweet($state{__tweets}{ lc $nick }[$id], not $settings{rt_to_expand}));
 
     Irssi::command("msg $target $text");
 
@@ -393,6 +394,7 @@ sub cmd_retweet_to_window {
 
 sub cmd_reload {
     if ($settings{force_first} and $settings{poll_store}) {
+        &save_state();
         &save_polls();
     }
     Irssi::command("script load $IRSSI{name}");
@@ -517,7 +519,11 @@ sub cmd_info {
     my $tweet         = $state{__tweets}{$nick}[$id];
     my $reply_to_id   = $state{__reply_to_ids}{$nick}[$id];
     my $reply_to_user = $state{__reply_to_users}{$nick}[$id];
-    my $exp_tweet     = &unshorten($tweet) if $tweet;
+    my $exp_tweet     = $tweet;
+    if ($tweet) {
+        $tweet        = &post_process_tweet($tweet, 1);
+        $exp_tweet    = &post_process_tweet($exp_tweet);
+    }
 
     my $url = '';
     if ( defined $username ) {
@@ -588,7 +594,7 @@ sub cmd_reply_as {
         return;
     }
 
-    $id = $state{__indexes}{lc $nick} unless $id;
+    $id = $state{__indexes}{lc $nick} unless defined $id;
     unless ( $state{__ids}{ lc $nick }[$id] ) {
         &notice( [ "reply", $username ],
             "Can't find a tweet numbered $id from $nick to reply to!" );
@@ -664,7 +670,7 @@ sub gen_cmd {
             return;
         }
 
-        &$post_ref($data) if $post_ref;
+        &$post_ref($data, $server, $win) if $post_ref;
       }
 }
 
@@ -810,14 +816,6 @@ sub cmd_login {
     } elsif ( @{ $settings{usernames} } and @{ $settings{passwords} } ) {
         &debug("autouser login");
 
-        # if a password ends with a '\', it was meant to escape the comma, and
-        # it should be concatinated with the next one
-        for (my $i = 0;  $i+1 < @{ $settings{passwords} };  $i++) {
-            while ( $settings{passwords}->[$i] =~ /\\$/ ) {
-                $settings{passwords}->[$i] .= "," . delete $settings{passwords}->[$i+1];
-            }
-        }
-
         if ( @{ $settings{usernames} } != @{ $settings{passwords} } ) {
             &notice( ["error"],
                     "Number of usernames doesn't match "
@@ -861,7 +859,7 @@ sub cmd_login {
             } else {
                 $twit = Net::Twitter->new(
                     traits =>
-                      [ 'API::REST', 'OAuth', 'API::Search', 'API::Lists', 'RetryOnError' ],
+                      [ 'API::RESTv1_1', 'OAuth', 'RetryOnError' ],
                     (
                         grep tr/a-zA-Z/n-za-mN-ZA-M/, map $_,
                         pbafhzre_xrl => 'OMINiOzn4TkqvEjKVioaj',
@@ -955,7 +953,7 @@ sub cmd_oauth {
     };
 
     if ($@) {
-        &notice( ["error"], "Invalid pin, try again." );
+        &notice( ["error"], "Invalid pin, try again: $@" );
         return;
     }
 
@@ -1002,12 +1000,19 @@ sub rate_limited {
     eval {
         $rate_limit = $obj->rate_limit_status();
     };
-    if ( $rate_limit and $rate_limit->{remaining_hits} < 1 ) {
-        &notice( [ 'error', $username, $fh ],
-            "Rate limit exceeded, try again after $rate_limit->{reset_time}" );
-        return 1;
+    my $res = 0;
+    if ( $rate_limit and $rate_limit->{resources} ) {
+        for my $resource (keys %{ $rate_limit->{resources} }) {
+            for my $uri (keys %{ $rate_limit->{resources}->{$resource} }) {
+                if ( $rate_limit->{resources}->{$resource}->{$uri}->{remaining} < 1 ) {
+                    &notice( [ 'error', $username, $fh ],
+                        "Rate limit exceeded for $resource ($uri), try again after $rate_limit->{resources}->{$resource}->{$uri}->{reset}" );
+                    $res = 1;
+                }
+            }
+        }
     }
-    return 0;
+    return $res;
 }
 
 sub verify_twitter_object {
@@ -1148,7 +1153,11 @@ sub cmd_add_search {
 
     $state{__last_id}{"$user\@$defservice"}{__search}{$data} = 1;
     &notice( [ "search", $data ], "Added subscription for '$data'" );
-    &cmd_set_window("search $data $data", $server, $win) if $want_win;
+    if ($want_win) {
+        my $win_name = $data;
+        $win_name =~ tr/ /+/;
+        &cmd_set_window("search $data $win_name", $server, $win);
+    }
 }
 
 sub cmd_del_search {
@@ -1182,11 +1191,11 @@ sub cmd_list_search {
 
     my $found = 0;
     foreach my $suser ( sort keys %{ $state{__last_id} } ) {
-        my $topics;
+        my $topics = '';
         foreach my $topic ( sort keys %{ $state{__last_id}{$suser}{__search} } ) {
-            $topics = $topics ? "$topics, $topic" : $topic;
+            $topics .= ($topics ne '' ? ', ' : '') . "'$topic'";
         }
-        if ($topics) {
+        if ($topics ne '') {
             $found = 1;
             &notice( ["search"], "Search subscriptions for $suser: $topics" );
         }
@@ -1415,7 +1424,7 @@ sub cmd_set_window {
         &notice("Changing the default twirssi window to $winname");
         Irssi::settings_set_str( "twitter_window", $winname );
         &ensure_logfile($settings{window} = $winname);
-     } elsif ( @words > 2 ) {
+     } elsif ( @words > 2 and $words[0] ne 'search' ) {
         &notice(
                 "Too many arguments to /twirssi_set_window. '@words'",
                 "Usage: /twirssi_set_window [type] [account|search_term] [window].",
@@ -1433,7 +1442,7 @@ sub cmd_set_window {
         }
 
         my $tag = "default";
-        if ( @words == 2 ) {
+        if ( @words >= 2 ) {
            $tag = lc $words[1];
            if ($type eq 'sender') {
               $tag =~ s/^\@//;
@@ -1442,6 +1451,8 @@ sub cmd_set_window {
                    and ($type ne 'default' or index($tag, '@') >= 0)
                    and $tag ne 'default') {
               $tag = &normalize_username($tag);
+           } elsif ($type eq 'search' and @words > 2) {
+              $tag = lc join(' ', @words[1..$#words]);
            }
            if (substr($tag, -1, 1) eq '@') {
               &notice(['error'], "Invalid tag '$tag'.");
@@ -1577,7 +1588,7 @@ sub get_lists {
     # ensure $new_lists->{$list_name} = $id
     my %more_args = ();
     my $new_lists = &scan_cursor('lists', $u_twit, $username, $fh,
-				{ fn=>'get_lists', cp=>'c', set_key=>'lists',
+				{ fn=>'get_lists', cp=>'', set_key=>'lists',
 					args=>{ user=>$userid, %more_args }, item_key=>'name', item_val=>'id', });
     return if not defined $new_lists;
 
@@ -1625,7 +1636,7 @@ sub get_blocks {
     my $is_update = shift;
 
     my $new_blocks = &scan_cursor('blocks', $u_twit, $username, $fh,
-				{ fn=>'blocking', cp=>'p', item_key=>'screen_name', });
+				{ fn=>'blocking', cp=>'c', set_key=>'users', item_key=>'screen_name', });
     return if not defined $new_blocks;
 
     return $new_blocks if not $is_update;
@@ -1707,7 +1718,11 @@ sub cmd_wipe {
 
 sub cmd_user {
     my $target = shift;
+    my $server = shift;
+    my $win = shift;
     $target =~ s/(?::\d+)?\s*$//;
+    &cmd_set_window("sender $target $target", $server, $win)
+                        if $target =~ s/^\s*-w\s+// and $target ne '';
     &get_updates([ 0, [
                             [ "$user\@$defservice", { up_user => $target } ],
                       ],
@@ -1724,8 +1739,7 @@ sub tweet_to_meta {
         username => $username,
         type     => $type,
         nick     => ($type eq 'dm' ? $t->{sender_screen_name}
-                                    : ($type =~ /^search/ ? $t->{from_user}
-                                                          : $t->{user}{screen_name})),
+                                    : $t->{user}{screen_name}),
     );
     ($meta{account}, $meta{service}) = split('@', $username, 2);
     foreach my $meta_key (keys %meta_to_twit) {
@@ -1791,7 +1805,7 @@ sub background_setup {
 
     if ($child_pid) {                   # parent
         Irssi::timeout_add_once( $pause_monitor, 'monitor_child',
-            [ $done_filename, $max_pauses, $pause_monitor, $is_update ] );
+            [ $done_filename, $max_pauses, $pause_monitor, $is_update, $filename . '.' . $child_pid, 0 ] );
         Irssi::pidwait_add($child_pid);
     } elsif ( defined $child_pid ) {    # child
         my $pid_filename = $filename . '.' . $$;
@@ -1876,6 +1890,7 @@ sub get_updates_child {
     my $time_before_update = time;
 
     my $error = 0;
+    my @error_types = ();
     my %context_cache;
 
     foreach my $update_tuple ( @$to_be_updated ) {
@@ -1885,14 +1900,20 @@ sub get_updates_child {
 
         if (0 == keys(%$what_to_update)
                 or defined $what_to_update->{up_tweets}) {
-            $error++ unless &get_tweets( $fh, $username, $twits{$username}, \%context_cache );
+            unless (&get_tweets( $fh, $username, $twits{$username}, \%context_cache )) {
+                $error++;
+                push @error_types, 'tweets';
+            }
 
             if ( exists $state{__last_id}{$username}{__extras}
                     and keys %{ $state{__last_id}{$username}{__extras} } ) {
                 my @frusers = sort keys %{ $state{__last_id}{$username}{__extras} };
 
-                $error++ unless &get_timeline( $fh, $frusers[ $fix_replies_index{$username} ],
-                                               $username, $twits{$username}, \%context_cache, $is_regular );
+                unless (&get_timeline( $fh, $frusers[ $fix_replies_index{$username} ],
+                                               $username, $twits{$username}, \%context_cache, $is_regular )) {
+                    $error++;
+                    push @error_types, 'replies';
+                }
 
                 $fix_replies_index{$username}++;
                 $fix_replies_index{$username} = 0
@@ -1904,27 +1925,39 @@ sub get_updates_child {
         next if $error > $errors_beforehand;
 
         if (defined $what_to_update->{up_user}) {
-            $error++ unless &get_timeline( $fh, $what_to_update->{up_user},
-                                               $username, $twits{$username}, \%context_cache, $is_regular );
+            unless (&get_timeline( $fh, $what_to_update->{up_user},
+                                               $username, $twits{$username}, \%context_cache, $is_regular )) {
+                $error++;
+                push @error_types, 'tweets';
+            }
 
         }
         next if $error > $errors_beforehand;
 
         if (0 == keys(%$what_to_update)
                     or defined $what_to_update->{up_dms}) {
-            $error++ unless &do_dms( $fh, $username, $twits{$username}, $is_regular );
+            unless (&do_dms( $fh, $username, $twits{$username}, $is_regular )) {
+                $error++;
+                push @error_types, 'dms';
+            }
         }
         next if $error > $errors_beforehand;
 
         if (0 == keys(%$what_to_update)
                     or defined $what_to_update->{up_subs}) {
-            $error++ unless &do_subscriptions( $fh, $username, $twits{$username}, $what_to_update->{up_subs} );
+            unless (&do_subscriptions( $fh, $username, $twits{$username}, $what_to_update->{up_subs} )) {
+                $error++;
+                push @error_types, 'subs';
+            }
         }
         next if $error > $errors_beforehand;
 
         if (0 == keys(%$what_to_update)
                     or defined $what_to_update->{up_searches}) {
-            $error++ unless &do_searches( $fh, $username, $twits{$username}, $what_to_update->{up_searches} );
+            unless (&do_searches( $fh, $username, $twits{$username}, $what_to_update->{up_searches} )) {
+                $error++;
+                push @error_types, 'searches';
+            }
         }
         next if $error > $errors_beforehand;
 
@@ -1990,6 +2023,7 @@ sub get_updates_child {
                 if (not defined &get_lists($twits{$username}, $username, $fh, 0, @{ $what_to_update->{up_lists} })) {
                     &debug($fh, "%G$username%n Polling for lists failed.");
                     $error++;
+                    push @error_types, 'lists';
                 }
             }
             if (not defined $state{__lists}{$list_account}) {
@@ -2014,7 +2048,8 @@ sub get_updates_child {
     &put_unshorten_urls($fh, $time_before_update);
 
     if ($error) {
-        &notice( [ 'error', undef, $fh ], "Update encountered errors.  Aborted");
+        &notice( [ 'error', undef, $fh ], "Update encountered errors (@error_types).  Aborted");
+        &notice( [ 'error', undef, $fh ], "For recurring DMs errors, please re-auth (delete $settings{oauth_store})") if grep { $_ eq 'dms' } @error_types;
     } elsif ($is_regular) {
         print $fh "t:last_poll poll_type:__poll epoch:$time_before_update\n";
     }
@@ -2050,15 +2085,14 @@ sub get_tweets {
     return if &rate_limited($obj, $username, $fh);
 
     &debug($fh, "%G$username%n Polling for tweets");
-    my $tweets;
-    my $new_poll_id = 0;
+    my $tweets = [];
     eval {
         my %call_attribs = ( page => 1 );
         $call_attribs{count} = $settings{track_replies} if $settings{track_replies};
-        # $call_attribs{since_id} = $state{__last_id}{$username}{timeline}
-                           # if defined $state{__last_id}{$username}{timeline};
+        $call_attribs{since_id} = $state{__last_id}{$username}{timeline}
+                           if defined $state{__last_id}{$username}{timeline};
         for ( ; $call_attribs{page} < 2 ; $call_attribs{page}++) {
-            &debug($fh, "%G$username%n timeline pg=" . $call_attribs{page});
+            &debug($fh, "%G$username%n timeline " . join(' ', map { $_ . '=' . $call_attribs{$_} } sort keys %call_attribs));
             my $page_tweets = $obj->home_timeline( \%call_attribs );
             last if not defined $page_tweets or @$page_tweets == 0;
             unshift @$tweets, @$page_tweets;
@@ -2071,6 +2105,9 @@ sub get_tweets {
         return;
     }
 
+
+=pod
+
     unless ( ref $tweets ) {
         if ( $obj->can("get_error") ) {
             my $error = "Unknown error";
@@ -2082,15 +2119,22 @@ sub get_tweets {
 
         } else {
             &notice([ 'error', $username, $fh],
-                "$username: API Error during home_timeline call. Aborted.");
+                "$username: API Error in home_timeline call. Aborted.");
         }
         return;
     }
 
-    print $fh "t:debug %G$username%n got ", scalar(@$tweets), " tweets, first/last: ",
-                        (sort {$a->{id} <=> $b->{id}} @$tweets)[0]->{id}, "/",
-                        (sort {$a->{id} <=> $b->{id}} @$tweets)[$#{$tweets}]->{id}, "\n";
+=cut
+
+    print $fh "t:debug %G$username%n got ", scalar(@$tweets), ' tweets',
+		(@$tweets	? ', first/last: ' . join('/',
+						(sort {$a->{id} <=> $b->{id}} @$tweets)[0]->{id},
+						(sort {$a->{id} <=> $b->{id}} @$tweets)[$#{$tweets}]->{id}
+					)
+				: ''),
+		"\n";
 
+    my $new_poll_id = 0;
     my @own_ids = ();
     foreach my $t ( reverse @$tweets ) {
         my $text = &get_text( $t, $obj );
@@ -2110,10 +2154,9 @@ sub get_tweets {
         $new_poll_id = $t->{id} if $new_poll_id < $t->{id};
     }
     &debug($fh, "%G$username%n skip own " . join(', ', @own_ids) . "\n") if @own_ids;
-    printf $fh "t:last_id id:%s ac:%s id_type:timeline\n", $new_poll_id, $username;
+    printf $fh "t:last_id id:%s ac:%s id_type:timeline\n", $new_poll_id, $username if $new_poll_id;
 
     &debug($fh, "%G$username%n Polling for replies since " . $state{__last_id}{$username}{reply});
-    $new_poll_id = 0;
     eval {
         if ( $state{__last_id}{$username}{reply} ) {
             $tweets = $obj->replies( { since_id => $state{__last_id}{$username}{reply} } )
@@ -2125,9 +2168,11 @@ sub get_tweets {
 
     if ($@) {
         print $fh "t:debug %G$username%n Error during replies call.  Aborted.\n";
+        &debug($fh, "%G$username%n Error: " . $@);
         return;
     }
 
+    $new_poll_id = 0;
     foreach my $t ( reverse @$tweets ) {
         next if exists $friends{$username}{ $t->{user}{screen_name} };
 
@@ -2141,7 +2186,7 @@ sub get_tweets {
           $t->{id}, $username, $ign, &get_reply_to($t), $t->{user}{screen_name},
           &encode_for_file($t->{created_at}), $text;
     }
-    printf $fh "t:last_id id:%s ac:%s id_type:reply\n", $new_poll_id, $username;
+    printf $fh "t:last_id id:%s ac:%s id_type:reply\n", $new_poll_id, $username if $new_poll_id;
     return 1;
 }
 
@@ -2166,6 +2211,7 @@ sub do_dms {
     };
     if ($@) {
         &debug($fh, "%G$username%n Error during direct_messages call.  Aborted.");
+        &debug($fh, "%G$username%n Error: " . $@);
         return;
     }
     &debug($fh, "%G$username%n got DMs: " . (0+@$tweets));
@@ -2178,7 +2224,7 @@ sub do_dms {
           &encode_for_file($t->{created_at}), $text;
         $new_poll_id = $t->{id} if $new_poll_id < $t->{id};
     }
-    printf $fh "t:last_id id:%s ac:%s id_type:dm\n", $new_poll_id, $username;
+    printf $fh "t:last_id id:%s ac:%s id_type:dm\n", $new_poll_id, $username if $new_poll_id;
     return 1;
 }
 
@@ -2204,28 +2250,29 @@ sub do_subscriptions {
             if ($@) {
                 print $fh
                   "t:debug %G$username%n Error during search($topic) call.  Aborted.\n";
+                &debug($fh, "%G$username%n Error: " . $@);
                 return;
             }
 
-            unless ( $search->{max_id} ) {
+            unless ( $search->{search_metadata}->{max_id} ) {
                 print $fh "t:debug %G$username%n Invalid search results when searching",
-                  " for $topic. Aborted.\n";
+                  " for '$topic'. Aborted.\n";
                 return;
             }
 
-            $state{__last_id}{$username}{__search}{$topic} = $search->{max_id};
+            $state{__last_id}{$username}{__search}{$topic} = $search->{search_metadata}->{max_id};
             printf $fh "t:searchid id:%s ac:%s topic:%s\n",
-              $search->{max_id}, $username, &encode_for_file($topic);
+              $search->{search_metadata}->{max_id}, $username, &encode_for_file($topic);
 
-            foreach my $t ( reverse @{ $search->{results} } ) {
-                next if exists $blocks{$username}{ $t->{from_user} };
+            foreach my $t ( reverse @{ $search->{statuses} } ) {
+                next if exists $blocks{$username}{ $t->{user}->{screen_name} };
                 my $text = &get_text( $t, $obj );
                 $text = &remove_tags($text);
-                my $ign = &is_ignored($text, $t->{from_user});
+                my $ign = &is_ignored($text, $t->{user}->{screen_name});
                 &get_unshorten_urls($text, $fh);
                 $ign = (defined $ign ? 'ign:' . &encode_for_file($ign) . ' ' : '');
                 printf $fh "t:search id:%s ac:%s %snick:%s topic:%s created_at:%s %s\n",
-                  $t->{id}, $username, $ign, $t->{from_user}, $topic,
+                  $t->{id}, $username, $ign, $t->{user}->{screen_name}, &encode_for_file($topic),
                   &encode_for_file($t->{created_at}), $text;
             }
         }
@@ -2243,16 +2290,20 @@ sub do_searches {
             next if defined $search_limit and @$search_limit and not grep { $topic eq $_ } @$search_limit;
             my $max_results = $search_once{$username}->{$topic};
 
+            $topic = &make_utf8($topic);
+
             print $fh
               "t:debug %G$username%n search $topic once (max $max_results)\n";
             eval { $search = $obj->search( { 'q' => $topic } ); };
 
-            if ($@) {
+            if (my $err = $@) {
+                $err = $err->error . ' (' . $err->code . ' ' . $err->message . ')' if ref($err) eq 'Net::Twitter::Error';
                 print $fh "t:debug %G$username%n Error during search_once($topic) call.  Aborted.\n";
+                &debug($fh, "%G$username%n Error: $err");
                 return;
             }
 
-            unless ( $search->{max_id} ) {
+            unless ( $search->{search_metadata}->{max_id} ) {
                 print $fh "t:debug %G$username%n Invalid search results when searching once",
                   " for $topic. Aborted.\n";
                 return;
@@ -2260,9 +2311,9 @@ sub do_searches {
 
             # TODO: consider applying ignore-settings to search results
             my @results = ();
-            foreach my $res (@{ $search->{results} }) {
-                if (exists $blocks{$username}{ $res->{from_user} }) {
-                    print $fh "t:debug %G$username%n blocked $topic: $res->{from_user}\n";
+            foreach my $res (@{ $search->{statuses} }) {
+                if (exists $blocks{$username}{ $res->{user}->{screen_name} }) {
+                    print $fh "t:debug %G$username%n blocked $topic: $res->{user}->{screen_name}\n";
                     next;
                 }
                 push @results, $res;
@@ -2274,10 +2325,10 @@ sub do_searches {
                 my $text = &get_text( $t, $obj );
                 $text = &remove_tags($text);
                 &get_unshorten_urls($text, $fh);
-                my $ign = &is_ignored($text, $t->{from_user});
+                my $ign = &is_ignored($text, $t->{user}->{screen_name});
                 $ign = (defined $ign ? 'ign:' . &encode_for_file($ign) . ' ' : '');
                 printf $fh "t:search_once id:%s ac:%s %s%snick:%s topic:%s created_at:%s %s\n",
-                  $t->{id}, $username, $ign, &get_reply_to($t), $t->{from_user}, &encode_for_file($topic),
+                  $t->{id}, $username, $ign, &get_reply_to($t), $t->{user}->{screen_name}, &encode_for_file($topic),
                   &encode_for_file($t->{created_at}), $text;
             }
         }
@@ -2297,6 +2348,8 @@ sub get_timeline {
     if ($is_update) {
         $arg_ref->{since_id} = $last_id if $last_id;
         $arg_ref->{include_rts} = 1 if $settings{retweet_show};
+    } elsif ($settings{limit_user_tweets} and $settings{limit_user_tweets} =~ /\b(\d+)\b/) {
+        $arg_ref->{count} = $1;
     }
     eval {
         $tweets = $obj->user_timeline($arg_ref);
@@ -2313,7 +2366,9 @@ sub get_timeline {
         return 1;
     }
 
+    my $not_before = time - $1*86400 if not $is_update and $settings{limit_user_tweets} and $settings{limit_user_tweets} =~ /\b(\d+)d\b/;
     foreach my $t ( reverse @$tweets ) {
+        next if defined $not_before and &date_to_epoch($t->{created_at}) < $not_before;
         my $text = &get_text( $t, $obj );
         my $reply = &tweet_or_reply($obj, $t, $username, $cache, $fh);
         printf $fh "t:%s id:%s ac:%s %snick:%s created_at:%s %s\n",
@@ -2378,7 +2433,7 @@ sub meta_to_line {
             level    => MSGLEVEL_PUBLIC,
     );
 
-    if ($meta->{type} eq 'dm' or $meta->{type} eq 'error') {
+    if ($meta->{type} eq 'dm' or $meta->{type} eq 'error' or $meta->{type} eq 'deerror') {
         $line_attribs{level} = MSGLEVEL_MSGS;
     }
 
@@ -2388,6 +2443,10 @@ sub meta_to_line {
         $line_attribs{level}  |= MSGLEVEL_HILIGHT;
         $line_attribs{hi_nick} = "\cC$hilight_color$meta->{nick}\cO";
     }
+    elsif ($settings{nick_color} eq 'rotate') {
+        my $c = get_nick_color($meta->{nick});
+        $line_attribs{hi_nick} = "\cC$c$meta->{nick}\cO";
+    }
 
     if (defined $meta->{ign}) {
         $line_attribs{ignoring} = 1;
@@ -2449,8 +2508,16 @@ sub monitor_child {
     my $attempts_to_go = $args->[1];
     my $wait_time      = $args->[2];
     my $is_update      = $args->[3];
+    my $filename_tmp   = $args->[4];
+    my $prev_mtime     = $args->[5];
 
-    &debug("checking child log at $filename ($attempts_to_go)");
+    my $file_progress = 'no ' . $filename_tmp;
+    my $this_mtime = $prev_mtime;
+    if (-f $filename_tmp) {
+        $this_mtime = (stat(_))[9];
+        $file_progress = 'mtime=' . $this_mtime;
+    }
+    &debug("checking child log at $filename [$file_progress v $prev_mtime] ($attempts_to_go)");
 
     # reap any random leftover processes - work around a bug in irssi on gentoo
     waitpid( -1, WNOHANG );
@@ -2468,32 +2535,92 @@ sub monitor_child {
     if ( -e $filename and open $fh, '<', $filename ) {
         binmode $fh, ":" . &get_charset();
     } else {
-        undef $fh;
+        # file not ready yet
+
+        if ( $attempts_to_go > 0 ) {
+            Irssi::timeout_add_once( $wait_time, 'monitor_child',
+                [ $filename, $attempts_to_go - 1, $wait_time, $is_update, $filename_tmp, $this_mtime ] );
+        } else {
+            &debug("Giving up on polling $filename");
+            Irssi::pidwait_remove($child_pid);
+            waitpid( -1, WNOHANG );
+            unlink $filename unless &debug();
+
+            if (not $is_update) {
+                &notice([ 'error' ], "Failed to get response.  Giving up.");
+                return;
+            }
+
+            $update_is_running = 0 if $is_update;
+
+            return unless $settings{notify_timeouts};
+
+            my $since;
+            if ( time - $last_poll{__poll} < 24 * 60 * 60 ) {
+                my @time = localtime($last_poll{__poll});
+                $since = sprintf( "%d:%02d", @time[ 2, 1 ] );
+            } else {
+                $since = scalar localtime($last_poll{__poll});
+            }
+
+            if ( $failstatus < 2 and time - $last_poll{__poll} > 60 * 60 ) {
+                &notice([ 'error' ],
+                  $settings{mini_whale}
+                  ? 'FAIL WHALE'
+                  : q{     v  v        v},
+                    q{     |  |  v     |  v},
+                    q{     | .-, |     |  |},
+                    q{  .--./ /  |  _.---.| },
+                    q{   '-. (__..-"       \\},
+                    q{      \\          a    |},
+                    q{       ',.__.   ,__.-'/},
+                    q{         '--/_.'----'`}
+                );
+                $failstatus = 2;
+            }
+
+            if ( $failstatus == 0 and time - $last_poll{__poll} < 600 ) {
+                &notice([ 'error' ],"Haven't been able to get updated tweets since $since");
+                $failstatus = 1;
+            }
+        }
+
+        return;
     }
-    while (defined $fh and defined ($_ = <$fh>)) {
+
+    # make sure we're not in slurp mode
+    local $/ = "\n";
+    while (<$fh>) {
         unless (/\n$/) {    # skip partial lines
             &debug($fh, "Skipping partial line: $_");
             next;
         }
         chomp;
 
-        if (s/^t:debug\s+//) {
+        my $type;
+        if (s/^t:(\w+)\s+//) {
+            $type = $1;
+        } else {
+            &notice(['error'], "invalid: $_");
+            next;
+        }
+
+        if ($type eq 'debug') {
             &debug($_);
 
-        } elsif (s/^t:(error|info)\s+//) {
-            my $type = $1;
+        } elsif ($type =~ /^(error|info|deerror)$/) {
             $got_errors++ if $type eq 'error';
             &notice([$type], $_);
 
-        } elsif (s/^t:(url)\s+//) {
-            my %meta = &cache_to_meta($_, $1, [ qw/epoch https site uri/ ]);
+        } elsif ($type eq 'url') {
+            my %meta = &cache_to_meta($_, $type, [ qw/epoch https site uri/ ]);
             $expanded_url{$meta{site}}{$meta{https} ? 1 : 0}{$meta{uri}} = {
                 url => $meta{text},
                 epoch => $meta{epoch},
             };
 
-        } elsif (s/^t:(last_poll)\s+//) {
-            my %meta = &cache_to_meta($_, $1, [ qw/ac poll_type epoch/ ]);
+        } elsif ($type eq 'last_poll') {
+            my %meta = &cache_to_meta($_, $type, [ qw/ac poll_type epoch/ ]);
 
             if ( not defined $meta{ac} and $meta{poll_type} eq '__poll' ) {
                 $last_poll{$meta{poll_type}} = $meta{epoch};
@@ -2506,13 +2633,13 @@ sub monitor_child {
                $got_errors++;
             }
 
-        } elsif (s/^t:(fix_replies_index)\s+//) {
-            my %meta = &cache_to_meta($_, $1, [ qw/idx ac topic id_type/ ]);
+        } elsif ($type eq 'fix_replies_index') {
+            my %meta = &cache_to_meta($_, $type, [ qw/idx ac topic id_type/ ]);
             $fix_replies_index{ $meta{username} } = $meta{idx};
             &debug("%G$meta{username}%n fix_replies_index set to $meta{idx}");
 
-        } elsif (s/^t:(searchid|last_id|last_id_fixreplies)\s+//) {
-            my %meta = &cache_to_meta($_, $1, [ qw/id ac topic id_type/ ]);
+        } elsif ($type eq 'searchid' or $type eq 'last_id_fixreplies' or $type eq 'last_id') {
+            my %meta = &cache_to_meta($_, $type, [ qw/id ac topic id_type/ ]);
             if ( $meta{type} eq 'searchid' ) {
                 &debug("%G$meta{username}%n Search '$meta{topic}' got id $meta{id}");
                 if (not exists $state{__last_id}{ $meta{username} }{__search}{ $meta{topic} }
@@ -2530,8 +2657,8 @@ sub monitor_child {
                   if $state{__last_id}{ $meta{username} }{__extras}{ $meta{id_type} } < $meta{id};
             }
 
-        } elsif (s/^t:(tweet|dm|reply|search|search_once)\s+//x) {
-            my %meta = &cache_to_meta($_, $1, [ qw/id ac ign reply_to_user reply_to_id nick topic created_at/ ]);
+        } elsif ($type eq 'tweet' or $type eq 'dm' or $type eq 'reply' or $type eq 'search' or $type eq 'search_once') {	# cf theme_register
+            my %meta = &cache_to_meta($_, $type, [ qw/id ac ign reply_to_user reply_to_id nick topic created_at/ ]);
 
             if (exists $new_cache{ $meta{id} }) {
                 &debug("SKIP newly-cached $meta{id}");
@@ -2556,8 +2683,8 @@ sub monitor_child {
                 delete $search_once{ $meta{username} }->{ $meta{topic} };
             }
 
-        } elsif (s/^t:(friend|block|list)\s+//) {
-            my %meta = &cache_to_meta($_, $1, [ qw/ac list id nick epoch/ ]);
+        } elsif ($type eq 'friend' or $type eq 'block' or $type eq 'list') {
+            my %meta = &cache_to_meta($_, $type, [ qw/ac list id nick epoch/ ]);
             if ($is_update and not defined $types_per_user{$meta{username}}{$meta{type}}) {
                 if ($meta{type} eq 'friend') {
                     $friends{$meta{username}} = ();
@@ -2583,120 +2710,67 @@ sub monitor_child {
             }
 
         } else {
-            &notice(['error'], "invalid: $_");
+            &notice(['error'], "invalid type ($type): $_");
         }
     }
 
-    if (defined $fh) {
-        # file was opened, so we tried to parse...
-        close $fh;
+    # file was opened, so we tried to parse...
+    close $fh;
 
-        # make sure the pid is removed from the waitpid list
-        Irssi::pidwait_remove($child_pid);
+    # make sure the pid is removed from the waitpid list
+    Irssi::pidwait_remove($child_pid);
 
-        # and that we don't leave any zombies behind, somehow
-        waitpid( -1, WNOHANG );
+    # and that we don't leave any zombies behind, somehow
+    waitpid( -1, WNOHANG );
 
-        &debug("new last_poll    = $last_poll{__poll}",
-               "new last_poll_id = " . Dumper( $state{__last_id} )) if $is_update;
-        if ($is_update and $first_call and not $settings{force_first}) {
-            &debug("First call, not printing updates");
-        } else {
+    &debug("new last_poll    = $last_poll{__poll}",
+           "new last_poll_id = " . Dumper( $state{__last_id} )) if $is_update;
+    if ($is_update and $first_call and not $settings{force_first}) {
+        &debug("First call, not printing updates");
+    } else {
 
-            if (exists $show_now{lists}) {
-                for my $list_account (keys %{ $show_now{lists} }) {
-                    my $list_ac = ($list_account eq "$user\@$defservice" ? '' : "$list_account/");
-                    for my $list_name (keys %{ $show_now{lists}{$list_account} }) {
-                        if (0 == @{ $state{__lists}{$list_account}{$list_name}{members} }) {
-                            &notice(['info'], "List $list_ac$list_name is empty.");
-                        } else {
-                            &notice("List $list_ac$list_name members: " .
-                                    join(', ', @{ $state{__lists}{$list_account}{$list_name}{members} }));
-                        }
+        if (exists $show_now{lists}) {
+            for my $list_account (keys %{ $show_now{lists} }) {
+                my $list_ac = ($list_account eq "$user\@$defservice" ? '' : "$list_account/");
+                for my $list_name (keys %{ $show_now{lists}{$list_account} }) {
+                    if (0 == @{ $state{__lists}{$list_account}{$list_name}{members} }) {
+                        &notice(['info'], "List $list_ac$list_name is empty.");
+                    } else {
+                        &notice("List $list_ac$list_name members: " .
+                                join(', ', @{ $state{__lists}{$list_account}{$list_name}{members} }));
                     }
                 }
             }
-
-            &write_lines(\@lines, $is_update);
-        }
-
-        unlink $filename or warn "Failed to remove $filename: $!" unless &debug();
-
-        # commit the pending cache lines to the actual cache, now that
-        # we've printed our output
-        for my $updated_id (keys %new_cache) {
-            $tweet_cache{$updated_id} = $new_cache{$updated_id};
-        }
-
-        # keep enough cached tweets, to make sure we don't show duplicates
-        for my $loop_id ( keys %tweet_cache ) {
-            next if $tweet_cache{$loop_id} >= $last_poll{__poll} - 3600;
-            delete $tweet_cache{$loop_id};
-        }
-
-        if (not $got_errors) {
-            &save_state();
         }
 
-        if ($is_update) {
-            if ($failstatus and not $got_errors) {
-                &notice([ 'error' ], "Update succeeded.");
-                $failstatus    = 0;
-            }
-            $first_call        = 0;
-            $update_is_running = 0;
-        }
-        return;
+        &write_lines(\@lines, $is_update);
     }
 
-    # get here only if failed
+    unlink $filename or warn "Failed to remove $filename: $!" unless &debug();
 
-    if ( $attempts_to_go > 0 ) {
-        Irssi::timeout_add_once( $wait_time, 'monitor_child',
-            [ $filename, $attempts_to_go - 1, $wait_time, $is_update ] );
-    } else {
-        &debug("Giving up on polling $filename");
-        Irssi::pidwait_remove($child_pid);
-        waitpid( -1, WNOHANG );
-        unlink $filename unless &debug();
-
-        if (not $is_update) {
-            &notice([ 'error' ], "Failed to get response.  Giving up.");
-            return;
-        }
-
-        $update_is_running = 0 if $is_update;
+    # commit the pending cache lines to the actual cache, now that
+    # we've printed our output
+    for my $updated_id (keys %new_cache) {
+        $tweet_cache{$updated_id} = $new_cache{$updated_id};
+    }
 
-        return unless $settings{notify_timeouts};
+    # keep enough cached tweets, to make sure we don't show duplicates
+    for my $loop_id ( keys %tweet_cache ) {
+        next if $tweet_cache{$loop_id} >= $last_poll{__poll} - 3600;
+        delete $tweet_cache{$loop_id};
+    }
 
-        my $since;
-        if ( time - $last_poll{__poll} < 24 * 60 * 60 ) {
-            my @time = localtime($last_poll{__poll});
-            $since = sprintf( "%d:%02d", @time[ 2, 1 ] );
-        } else {
-            $since = scalar localtime($last_poll{__poll});
-        }
-
-        if ( $failstatus < 2 and time - $last_poll{__poll} > 60 * 60 ) {
-            &notice([ 'error' ],
-              $settings{mini_whale}
-              ? 'FAIL WHALE'
-              : q{     v  v        v},
-                q{     |  |  v     |  v},
-                q{     | .-, |     |  |},
-                q{  .--./ /  |  _.---.| },
-                q{   '-. (__..-"       \\},
-                q{      \\          a    |},
-                q{       ',.__.   ,__.-'/},
-                q{         '--/_.'----'`}
-            );
-            $failstatus = 2;
-        }
+    if (not $got_errors) {
+        &save_state();
+    }
 
-        if ( $failstatus == 0 and time - $last_poll{__poll} < 600 ) {
-            &notice([ 'error' ],"Haven't been able to get updated tweets since $since");
-            $failstatus = 1;
+    if ($is_update) {
+        if ($failstatus and not $got_errors) {
+            &notice([ 'deerror' ], "Update succeeded.");
+            $failstatus    = 0;
         }
+        $first_call        = 0;
+        $update_is_running = 0;
     }
 }
 
@@ -2726,7 +2800,7 @@ sub write_lines {
         );
         push @print_opts, (lc $line->{topic} ne lc $win_name ? $line->{topic} . ':' : '')
           if $line->{type} =~ /search/;
-        push @print_opts, $line->{hi_nick} if $line->{type} ne 'error';
+        push @print_opts, $line->{hi_nick} if $line->{type} ne 'error' and $line->{type} ne 'deerror';
         push @print_opts, $line->{marker} if defined $line->{marker};
 
         # set timestamp
@@ -2755,7 +2829,7 @@ sub write_lines {
         if (not defined $old_tf) {
             $old_tf = Irssi::settings_get_str('timestamp_format');
         }
-        $line->{text} = &unshorten($line->{text});
+        $line->{text} = &post_process_tweet($line->{text});
         Irssi::command("^set timestamp_format $ts");
         Irssi::window_find_name($win_name)->printformat(
             @print_opts, &hilight( $line->{text} ) . $ymd_suffix
@@ -2910,9 +2984,10 @@ sub debug {
 }
 
 sub notice {
-    my ( $type, $tag, $fh );
+    my ( $type, $tag, $fh, $theme );
     if ( ref $_[0] ) {
         ( $type, $tag, $fh ) = @{ shift @_ };
+        $theme = 'twirssi_' . $type;
     }
     foreach my $msg (@_) {
         if (defined $fh) {
@@ -2933,8 +3008,8 @@ sub notice {
                 $win = Irssi::window_find_name(&window( $type, $tag ));
             }
 
-            if ($type =~ /^(error|info)$/) {
-                $win->printformat(MSGLEVEL_PUBLIC, 'twirssi_'.$type, $msg); # theme
+            if ($type =~ /^(error|info|deerror)$/) {
+                $win->printformat(MSGLEVEL_PUBLIC, $theme, $msg); # theme
             } else {
                 $win->print("${col}***%n $msg", $win_level );
             }
@@ -3168,6 +3243,14 @@ sub event_setup_changed {
                         $settings{$setting->[0]} = [ ];
                     } else {
                         $settings{$setting->[0]} = [ split($re, $settings{$setting->[0]}) ];
+                        if (grep { $_ eq $setting->[0] } ('passwords')) {
+                            # ends '\', unescape separator:  concatenate with next
+                            for (my $i = 0;  $i+1 < @{ $settings{$setting->[0]} };  $i++) {
+                                while ( $settings{$setting->[0]}->[$i] =~ /\\$/ ) {
+                                    $settings{$setting->[0]}->[$i] .= "," . delete $settings{$setting->[0]}->[$i+1];
+                                }
+                            }
+                        }
                     }
                     $is_list = 1;
                 } elsif ($pre_proc =~ s/^norm_user(?:,|$)//) {
@@ -3181,7 +3264,7 @@ sub event_setup_changed {
                     my @normed = ();
                     for my $to_norm ($is_list ? @{ $settings{$setting->[0]} } : $settings{$setting->[0]} ) {
                         next if $to_norm eq '';
-&debug($setting->[0] . ' to_norm {' . $to_norm . '}');
+                        &debug($setting->[0] . ' to_norm {' . $to_norm . '}');
                         push @normed, &normalize_username($to_norm, 1);
                     }
                     $is_list = 1;
@@ -3287,13 +3370,52 @@ sub get_charset {
     return $charset;
 }
 
+my @available_nick_colors =(
+    0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+    '0,2', '0,3', '0,5', '0,6',
+    '1,0', '1,3', '1,5', '1,6', '1,7', '1,10', '1,15',
+    '2,3', '2,7', '2,10', '2,15',
+    '3,2', '3,5', '3,10',
+    '4,2', '4,7',
+    '5,2', '5,3', '5,7', '5,10', '5,15',
+    '6,2', '6,7', '6,10', '6,15',
+    '8,2', '8,5', '8,6',
+    '9,2', '9,5', '9,6',
+    '10,2', '10,5', '10,6',
+    '11,2', '11,5', '11,6',
+    '12,2', '12,5',
+    '13,2', '13,15',
+    '14,2', '14,5', '14,6',
+    '15,2', '15,5', '15,6'
+);
+my %nick_colors;
+
+sub get_nick_color {
+    if ($settings{nick_color} eq 'rotate') {
+        my $nick = shift;
+
+        if (!defined $nick_colors{$nick}) {
+            my @chars = split //, lc $nick;
+            my $value = 0;
+            foreach my $char (@chars) {
+                $value += ord $char;
+            }
+            $nick_colors{$nick} = $available_nick_colors[$value % @available_nick_colors];
+        }
+        return $nick_colors{$nick};
+    } else {
+        return $irssi_to_mirc_colors{$settings{nick_color}};
+    }
+}
+
 sub hilight {
     my $text = shift;
 
     if ( $settings{nick_color} ) {
-        my $c = $settings{nick_color};
-        $c = $irssi_to_mirc_colors{$c};
-        $text =~ s/(^|\W)\@(\w+)/$1\cC$c\@$2\cO/g if $c;
+        $text =~ s[(^|\W)\@(\w+)] {
+            my $c = get_nick_color($2);
+            qq[$1\cC$c\@$2\cO];
+        }eg;
     }
     if ( $settings{topic_color} ) {
         my $c = $settings{topic_color};
@@ -3418,15 +3540,17 @@ sub put_unshorten_urls {
 }
 
 
-sub unshorten {
+sub post_process_tweet {
     my $data = shift;
-    return $data unless @{ $settings{url_unshorten} };
-    for my $site (keys %expanded_url) {
-        for my $https (keys %{ $expanded_url{$site} }) {
-            my $url = ($https ? 'https' : 'http') . '://' . $site . '/';
-            next if -1 == index($data, $url);
-            for my $uri (keys %{ $expanded_url{$site}{$https} }) {
-                $data =~ s/\Q$url$uri\E/$& \cC$irssi_to_mirc_colors{$settings{unshorten_color}}<$expanded_url{$site}{$https}{$uri}{url}>\cO/g;
+    my $skip_unshorten = shift;
+    if (@{ $settings{url_unshorten} } and not $skip_unshorten) {
+        for my $site (keys %expanded_url) {
+            for my $https (keys %{ $expanded_url{$site} }) {
+                my $url = ($https ? 'https' : 'http') . '://' . $site . '/';
+                next if -1 == index($data, $url);
+                for my $uri (keys %{ $expanded_url{$site}{$https} }) {
+                    $data =~ s/\Q$url$uri\E/$& \cC$irssi_to_mirc_colors{$settings{unshorten_color}}<$expanded_url{$site}{$https}{$uri}{url}>\cO/g;
+                }
             }
         }
     }
@@ -3466,14 +3590,13 @@ sub get_text {
     my $tweet  = shift;
     my $object = shift;
     my $text   = decode_entities( $tweet->{text} );
-    if ( $tweet->{truncated} ) {
-        if ( exists $tweet->{retweeted_status} ) {
-            $text = "RT \@$tweet->{retweeted_status}{user}{screen_name}: "
-              . "$tweet->{retweeted_status}{text}";
-        } elsif ( $object->isa('Net::Twitter') ) {
-            $text .= " -- http://twitter.com/$tweet->{user}{screen_name}"
-              . "/status/$tweet->{id}";
-        }
+    if ( exists $tweet->{retweeted_status} ) {
+        $text = &format_expand(fmt => $settings{retweeted_format} || $settings{retweet_format},
+                  nick => $tweet->{retweeted_status}{user}{screen_name}, data => '',
+                  tweet => decode_entities( $tweet->{retweeted_status}{text} ));
+    } elsif ( $tweet->{truncated} and $object->isa('Net::Twitter') ) {
+        $text .= " -- http://twitter.com/$tweet->{user}{screen_name}"
+          . "/status/$tweet->{id}";
     }
 
     $text =~ s/[\n\r]/ /g;
@@ -3488,6 +3611,7 @@ sub window {
     my $topic = lc(shift || '');
 
     $type = "search" if $type eq 'search_once';
+    $type = "error" if $type eq 'deerror';
 
     my $win;
     my @all_priorities = qw/ account sender list /;
@@ -3596,6 +3720,7 @@ Irssi::theme_register( # theme
         'twirssi_reply',       '[$0\--> %B@$1%n$2] $3',
         'twirssi_dm',          '[$0%r@$1%n (%WDM%n)] $2',
         'twirssi_error',       '%RERROR%n: $0',
+        'twirssi_deerror',     '%RUPDATE%n: $0',
         'twirssi_info',        '%CINFO:%N $0',
         'twirssi_new_day',     '%CDay changed to $0%N',
     ]
@@ -3741,6 +3866,7 @@ if ( Irssi::window_find_name(window()) ) {
                 &notice( ["tweet", "$user\@$defservice"],
                          "Following $_[0]" );
                 $nicks{ $_[0] } = time;
+                &cmd_user(@_);
             },
             sub {
                 &cmd_set_window("sender $_[0] $_[0]", $_[1], $_[2])


More information about the scm-commits mailing list