+ if ( my $timeout = Irssi::settings_get_int("twitter_timeout")
+ and $twit->can('ua') )
+ {
+ $twit->ua->timeout($timeout);
+ }
+
+ unless ( $twit->verify_credentials() ) {
+ ¬ice("Login as $user\@$service failed");
+ $twit = undef;
+ if ( keys %twits ) {
+ &cmd_switch( ( keys %twits )[0], $server, $win );
+ }
+ return;
+ }
+
+ if ($twit) {
+ my $rate_limit = $twit->rate_limit_status();
+ if ( $rate_limit and $rate_limit->{remaining_hits} < 1 ) {
+ ¬ice(
+ "Rate limit exceeded, try again after $rate_limit->{reset_time}"
+ );
+ $twit = undef;
+ return;
+ }
+
+ $twits{"$user\@$service"} = $twit;
+ Irssi::timeout_remove($poll) if $poll;
+ $poll = Irssi::timeout_add( &get_poll_time * 1000, \&get_updates, "" );
+ ¬ice("Logged in as $user\@$service, loading friends list...");
+ &load_friends();
+ ¬ice( "loaded friends: ", scalar keys %friends );
+ if ( Irssi::settings_get_bool("twirssi_first_run") ) {
+ Irssi::settings_set_bool( "twirssi_first_run", 0 );
+ unless ( exists $friends{twirssi} ) {
+ ¬ice("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_follow twirssi." );
+ }
+ }
+ %nicks = %friends;
+ $nicks{$user} = 0;
+ return 1;
+ } else {
+ ¬ice("Login failed");
+ }
+}
+
+sub cmd_add_search {
+ my ( $data, $server, $win ) = @_;
+
+ unless ( $twit and $twit->can('search') ) {
+ ¬ice("ERROR: Your version of Net::Twitter ($Net::Twitter::VERSION) "
+ . "doesn't support searches." );
+ return;
+ }
+
+ $data =~ s/^\s+|\s+$//;
+ $data = lc $data;
+
+ unless ($data) {
+ ¬ice("Usage: /twitter_subscribe <topic>");
+ return;
+ }
+
+ if ( exists $id_map{__searches}{"$user\@$defservice"}{$data} ) {
+ ¬ice("Already had a subscription for '$data'");
+ return;
+ }
+
+ $id_map{__searches}{"$user\@$defservice"}{$data} = 1;
+ ¬ice("Added subscription for '$data'");
+}
+
+sub cmd_del_search {
+ my ( $data, $server, $win ) = @_;
+
+ unless ( $twit and $twit->can('search') ) {
+ ¬ice("ERROR: Your version of Net::Twitter ($Net::Twitter::VERSION) "
+ . "doesn't support searches." );
+ return;
+ }
+ $data =~ s/^\s+|\s+$//;
+ $data = lc $data;
+
+ unless ($data) {
+ ¬ice("Usage: /twitter_unsubscribe <topic>");
+ return;
+ }
+
+ unless ( exists $id_map{__searches}{"$user\@$defservice"}{$data} ) {
+ ¬ice("No subscription found for '$data'");
+ return;
+ }
+
+ delete $id_map{__searches}{"$user\@$defservice"}{$data};
+ ¬ice("Removed subscription for '$data'");
+}
+
+sub cmd_list_search {
+ my ( $data, $server, $win ) = @_;
+
+ my $found = 0;
+ foreach my $suser ( sort keys %{ $id_map{__searches} } ) {
+ my $topics;
+ foreach my $topic ( sort keys %{ $id_map{__searches}{$suser} } ) {
+ $topics = $topics ? "$topics, $topic" : $topic;
+ }
+ if ($topics) {
+ $found = 1;
+ ¬ice("Search subscriptions for \@$suser: $topics");
+ }
+ }
+
+ unless ($found) {
+ ¬ice("No search subscriptions set up");
+ }
+}
+
+sub cmd_upgrade {
+ my ( $data, $server, $win ) = @_;
+
+ my $loc = Irssi::settings_get_str("twirssi_location");
+ unless ( -w $loc ) {
+ ¬ice(
+"$loc isn't writable, can't upgrade. Perhaps you need to /set twirssi_location?"
+ );
+ return;
+ }
+
+ my $md5;
+ unless ( $data or Irssi::settings_get_bool("twirssi_upgrade_beta") ) {
+ eval { use Digest::MD5; };
+
+ if ($@) {
+ ¬ice(
+"Failed to load Digest::MD5. Try '/twirssi_upgrade nomd5' to skip MD5 verification"
+ );
+ return;
+ }
+
+ $md5 = get("http://twirssi.com/md5sum");
+ chomp $md5;
+ $md5 =~ s/ .*//;
+ unless ($md5) {
+ ¬ice("Failed to download md5sum from peeron! Aborting.");
+ return;
+ }
+
+ unless ( open( CUR, $loc ) ) {
+ ¬ice(
+"Failed to read $loc. Check that /set twirssi_location is set to the correct location."
+ );
+ return;
+ }
+
+ my $cur_md5 = Digest::MD5::md5_hex(<CUR>);
+ close CUR;
+
+ if ( $cur_md5 eq $md5 ) {
+ ¬ice("Current twirssi seems to be up to date.");
+ return;
+ }
+ }
+
+ my $URL =
+ Irssi::settings_get_bool("twirssi_upgrade_beta")
+ ? "http://github.com/zigdon/twirssi/raw/master/twirssi.pl"
+ : "http://twirssi.com/twirssi.pl";
+ ¬ice("Downloading twirssi from $URL");
+ LWP::Simple::getstore( $URL, "$loc.upgrade" );
+
+ unless ( -s "$loc.upgrade" ) {
+ ¬ice("Failed to save $loc.upgrade."
+ . " Check that /set twirssi_location is set to the correct location."
+ );
+ return;
+ }
+
+ unless ( $data or Irssi::settings_get_bool("twirssi_upgrade_beta") ) {
+ unless ( open( NEW, "$loc.upgrade" ) ) {
+ ¬ice("Failed to read $loc.upgrade."
+ . " Check that /set twirssi_location is set to the correct location."
+ );
+ return;
+ }
+
+ my $new_md5 = Digest::MD5::md5_hex(<NEW>);
+ close NEW;
+
+ if ( $new_md5 ne $md5 ) {
+ ¬ice("MD5 verification failed. expected $md5, got $new_md5");
+ return;
+ }
+ }
+
+ rename $loc, "$loc.backup"
+ or ¬ice("Failed to back up $loc: $!. Aborting")
+ and return;
+ rename "$loc.upgrade", $loc
+ or ¬ice("Failed to rename $loc.upgrade: $!. Aborting")
+ and return;
+
+ my ( $dir, $file ) = ( $loc =~ m{(.*)/([^/]+)$} );
+ if ( -e "$dir/autorun/$file" ) {
+ ¬ice("Updating $dir/autorun/$file");
+ unlink "$dir/autorun/$file"
+ or ¬ice("Failed to remove old $file from autorun: $!");
+ symlink "../$file", "$dir/autorun/$file"
+ or ¬ice("Failed to create symlink in autorun directory: $!");
+ }
+
+ ¬ice("Download complete. Reload twirssi with /script load $file");
+}
+
+sub load_friends {
+ my $fh = shift;
+ my $page = 1;
+ my %new_friends;
+ eval {
+ while (1)
+ {
+ print $fh "type:debug Loading friends page $page...\n"
+ if ( $fh and &debug );
+ my $friends = $twit->friends( { page => $page } );
+ last unless $friends;
+ $new_friends{ $_->{screen_name} } = time foreach @$friends;
+ $page++;
+ last if @$friends == 0 or $page == 10;
+ }
+ };
+
+ if ($@) {
+ print $fh "type:debug Error during friends list update. Aborted.\n";
+ return;
+ }
+
+ my ( $added, $removed ) = ( 0, 0 );
+ print $fh "type:debug Scanning for new friends...\n" if ( $fh and &debug );
+ foreach ( keys %new_friends ) {
+ next if exists $friends{$_};
+ $friends{$_} = time;
+ $added++;
+ }
+
+ print $fh "type:debug Scanning for removed friends...\n"
+ if ( $fh and &debug );
+ foreach ( keys %friends ) {
+ next if exists $new_friends{$_};
+ delete $friends{$_};
+ $removed++;
+ }
+
+ return ( $added, $removed );
+}
+
+sub get_updates {
+ print scalar localtime, " - get_updates starting" if &debug;
+
+ $window =
+ Irssi::window_find_name( Irssi::settings_get_str('twitter_window') );
+ unless ($window) {
+ Irssi::active_win()
+ ->print( "Can't find a window named '"
+ . Irssi::settings_get_str('twitter_window')
+ . "'. Create it or change the value of twitter_window" );
+ }
+
+ return unless &logged_in($twit);
+
+ my ( $fh, $filename ) = File::Temp::tempfile();
+ binmode( $fh, ":utf8" );
+ my $pid = fork();
+
+ if ($pid) { # parent
+ Irssi::timeout_add_once( 5000, 'monitor_child',
+ [ "$filename.done", 0 ] );
+ Irssi::pidwait_add($pid);
+ } elsif ( defined $pid ) { # child
+ close STDIN;
+ close STDOUT;
+ close STDERR;
+
+ my $new_poll = time;
+
+ my $error = 0;
+ my %context_cache;
+ foreach ( keys %twits ) {
+ $error++ unless &do_updates( $fh, $_, $twits{$_}, \%context_cache );
+ }
+
+ 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";
+ } else {
+ print $fh "-- $new_poll";
+ }
+ close $fh;
+ rename $filename, "$filename.done";
+ exit;
+ } else {
+ &ccrap("Failed to fork for updating: $!");
+ }
+ print scalar localtime, " - get_updates ends" if &debug;
+}
+
+sub do_updates {
+ my ( $fh, $username, $obj, $cache ) = @_;
+
+ my $rate_limit = $obj->rate_limit_status();
+ if ( $rate_limit and $rate_limit->{remaining_hits} < 1 ) {
+ ¬ice("Rate limit exceeded for $username");
+ return undef;
+ }
+
+ print scalar localtime, " - Polling for updates for $username" if &debug;
+ my $tweets;
+ 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";
+ print $fh "type:debug : $_\n" foreach split /\n/, Dumper($@);
+ return undef;
+ }
+
+ unless ( ref $tweets ) {
+ if ( $obj->can("get_error") ) {
+ my $error = "Unknown error";
+ eval { $error = JSON::Any->jsonToObj( $obj->get_error() ) };
+ 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 undef;
+ }
+
+ foreach my $t ( reverse @$tweets ) {
+ my $text = decode_entities( $t->{text} );
+ $text =~ s/[\n\r]/ /g;
+ my $reply = "tweet";
+ if ( Irssi::settings_get_bool("show_reply_context")
+ and $t->{in_reply_to_screen_name} ne $username
+ and $t->{in_reply_to_screen_name}
+ and not exists $friends{ $t->{in_reply_to_screen_name} } )
+ {
+ $nicks{ $t->{in_reply_to_screen_name} } = time;
+ my $context;
+ 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 =~ s/[\n\r]/ /g;
+ if ( $context->{truncated} and ref($obj) ne 'Net::Identica' ) {
+ $ctext .=
+ " -- http://twitter.com/$context->{user}{screen_name}"
+ . "/status/$context->{id}";
+ }
+ printf $fh "id:%u account:%s nick:%s type:tweet %s\n",
+ $context->{id}, $username,
+ $context->{user}{screen_name}, $ctext;
+ $reply = "reply";
+ }
+ }
+ next
+ if $t->{user}{screen_name} eq $username
+ and not Irssi::settings_get_bool("show_own_tweets");
+ if ( $t->{truncated} and ref($obj) ne 'Net::Identica' ) {
+ $text .= " -- http://twitter.com/$t->{user}{screen_name}"
+ . "/status/$t->{id}";
+ }
+ printf $fh "id:%u 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};