# by Stefan "tommie" Tomanek # use strict; use vars qw($VERSION %IRSSI); $VERSION = '2017040101'; %IRSSI = ( authors => 'Stefan \'tommie\' Tomanek', contact => '[email protected]', name => 'Newsline', description => 'brings various newstickers to Irssi (Slashdot, Freshmeat, Heise etc.)', license => 'GPLv2', changed => $VERSION, modules => 'Data::Dumper XML::RSS LWP::UserAgent Unicode::String Text::Wrap', depends => 'openurl', sbitems => 'newsline_ticker', commands => 'newsline' ); use Irssi 20020324; use Irssi::TextUI; use Data::Dumper; use XML::RSS; use LWP::UserAgent; use POSIX; use Unicode::String qw(utf8 latin1); use Text::Wrap; use vars qw(@ticker $timestamp $slide $index $timer_cycle $timer_update %sites $forked); $index = 0; # Just to have some data for the first startup %sites = ( Heise=>{page => 'http://www.heise.de/newsticker/heise.rdf', enable => 1, title=>'', description=>'', maxnews=>0}, 'Freshmeat'=>{'page' => 'http://freshmeat.net/backend/fm.rdf', 'enable' => 1, title=>'', description=>'', maxnews=>0} ); sub show_help() { my $help = "newsline $VERSION /newsline List the downloaded headlines /newsline Open the entry indicated by via openurl. Openurl.pl is available at http://irssi.org/scripts/. /newsline description Display a brief summary of the article if available /newsline paste Write the headline and link to the current channel or query, add 'description' to a diplay the description as well /newsline fetch Retrieve new data from all enabled sources /newsline reload Reload configuration and sites /newsline save Save configration to ~/.irssi/newsline_sites /newsline list List all available sources /newsline toggle Enable or disable the source /newsline add Add a new source "; my $text=''; foreach (split(/\n/, $help)) { $_ =~ s/^\/(.*)$/%9\/$1%9/; $text .= $_."\n"; } print CLIENTCRAP &draw_box("Newsline", $text, "newsline help", 1); } sub fork_get() { my ($rh, $wh); pipe($rh, $wh); return if $forked; $forked = 1; my $pid = fork(); if ($pid > 0) { close $wh; Irssi::pidwait_add($pid); my $pipetag; my @args = ($rh, \$pipetag); $pipetag = Irssi::input_add(fileno($rh), INPUT_READ, \&pipe_input, \@args); } else { my (%siteinfo, @items); eval { foreach (sort keys %sites) { eval { my $site = $sites{$_}; next unless $site->{'enable'}; my $maxnews = -1; $maxnews = $site->{maxnews} if defined $site->{maxnews}; my $url = $site->{'page'}; my $ua = LWP::UserAgent->new(env_proxy=>1, keep_alive=>1, timeout=>30); my $request = HTTP::Request->new('GET', $url); #$request->if_modified_since($timestamp) if $timestamp; my $response = $ua->request($request); if ($response->is_success) { my $data = $response->content(); ### FIXME I hate myself for this :) $data =~ s/encoding="ISO-8859-15"/encoding="ISO-8859-1"/i; my $rss = new XML::RSS(); $rss->parse($data); my $title = $rss->{channel}->{title}; my $description = de_umlaut($rss->{channel}->{description}); my $link = de_umlaut($rss->{channel}->{link}); $siteinfo{$_} = {title=>$title, description=>$description, link=>$link}; foreach my $item (@{$rss->{items}}) { next unless defined($item->{title}) && defined($item->{'link'}); my $title = de_umlaut($item->{title}); $title =~ s/\n/ /g; my %story = ('title' => $title, 'link' => $item->{link}, 'source' => $_); $story{description} = de_umlaut($item->{description}) if $item->{description}; push @items, \%story; $maxnews--; last if $maxnews == 0; } }; } } my %result = (news=>\@items, siteinfo=>\%siteinfo); my $dumper = Data::Dumper->new([\%result]); $dumper->Purity(1)->Deepcopy(1); my $data = $dumper->Dump; print($wh $data); }; close($wh); POSIX::_exit(1); } } sub pipe_input { my ($rh, $pipetag) = @{$_[0]}; my $text; $text .= $_ foreach (<$rh>); close($rh); Irssi::input_remove($$pipetag); return unless($text); no strict; my %result = %{ eval "$text" }; my @items = @{$result{news}}; my %siteinfo = %{$result{siteinfo}}; @ticker = @items; foreach (sort keys %siteinfo) { $sites{$_}->{title} = $siteinfo{$_}->{title}; $sites{$_}->{description} = $siteinfo{$_}->{description}; $sites{$_}->{link} = $siteinfo{$_}->{link}; } $forked = 0; } sub draw_box ($$$$) { my ($title, $text, $footer, $colour) = @_; my $box = ''; $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n"; foreach (split(/\n/, $text)) { $box .= '%R|%n '.$_."\n"; } $box .= '%R`--<%n'.$footer.'%R>->%n'; $box =~ s/%.//g unless $colour; return $box; } sub cmd_newsline ($$$) { my ($args, $server, $witem) = @_; $args =~ s/^\ +//; my @arg = split(/\ +/, $args); if (scalar(@arg) == 0) { show_ticker(@ticker); } elsif ($arg[0] eq 'paste') { # paste tickernews shift(@arg); my $desc = 0; if (defined $arg[0] && $arg[0] eq 'description') { $desc = 1; shift(@arg); } foreach (@arg) { if (defined $ticker[$_-1]) { my $message = $ticker[$_-1]->{'title'}; my $text = '['.$ticker[$_-1]->{'source'}.'] "'.$message.'" -> '.$ticker[$_-1]->{'link'}; $Text::Wrap::columns = 50; my $article = wrap("","",$ticker[$_-1]->{description}) if ($desc && defined $ticker[$_-1]->{description}); my $text2 = draw_box($message, $article, $ticker[$_-1]->{source}, 0) if (defined $article); if (($witem) and (($witem->{type} eq "CHANNEL") or ($witem->{type} eq "QUERY"))) { $witem->command("MSG ".$witem->{name}." ".$text); if (defined $text2) { $witem->command("MSG ".$witem->{name}." ".$_) foreach (split /\n/, $text2); } } } } } elsif ($arg[0] eq 'description') { shift(@arg); foreach (@arg) { next unless defined $ticker[$_-1] and defined $ticker[$_-1]->{description}; $Text::Wrap::columns = 50; my $filter = $ticker[$_-1]->{description}; $filter =~ s/<.*?>//g; my $article = wrap("", "", $filter); my $text = ''; print CLIENTCRAP draw_box($ticker[$_-1]->{title}, $article, $ticker[$_-1]->{source}, 1); } } elsif ($arg[0] eq 'help') { show_help(); } elsif ($arg[0] eq 'fetch') { fork_get() } elsif ($arg[0] eq 'reload') { reload_config(); } elsif ($arg[0] eq 'save') { save_config(); } elsif ($arg[0] eq 'add') { if (defined($arg[1]) && defined($arg[2])) { my $source = $arg[1]; my $page = $arg[2]; $sites{$source} = {page => $page, enable => 1, maxnews=>0}; print CLIENTCRAP '%R>>%n Added new source "'.$arg[1].'"'; $timestamp = undef; } } elsif ($arg[0] eq 'delete') { if (defined $arg[1] && defined $sites{$arg[1]}) { delete $sites{$arg[1]}; print CLIENTCRAP "%R>>%n ".$arg[1]." deleted"; } } elsif ($arg[0] eq 'toggle') { # Toggle site if (defined $arg[1] && defined $sites{$arg[1]}) { if ($sites{$arg[1]}{'enable'} == 0) { $sites{$arg[1]}{'enable'} = 1; print CLIENTCRAP "%R>>%n ".$arg[1]." enabled"; } else { $sites{$arg[1]}{'enable'} = 0; print CLIENTCRAP "%R>>%n ".$arg[1]." disabled"; } } } elsif ($arg[0] eq 'limit') { if (defined $arg[1] && defined $sites{$arg[1]}) { if (defined $arg[2] && $arg[2] =~ /\d+/) { $sites{$arg[1]}{'maxnews'} = $arg[2]; print CLIENTCRAP "%R>>%n ".$arg[1]." limited to ".$arg[2]." articles"; } } } elsif ($arg[0] eq 'list') { my $text = ""; foreach (sort keys %sites) { my %site = %{$sites{$_}}; $text .= "%9[".$_.']%9'."\n"; $text .= " %9|-[page ]->%9 ".$site{'page'}."\n"; #$text .= " %9|-[desc ]->%9 ".$site{'description'}."\n" if defined $site{'description'}; $Text::Wrap::columns = 60; my $filter = $site{'description'}; $filter =~ s/<.*?>//; my $desc = wrap(" %9|-[desc ]->%9 ",' %9|%9', $filter); $desc =~ s// /g; $text .= $desc."\n" if $site{'description'}; $text .= " %9|-[limit ]->%9 ".$site{'maxnews'}."\n"; $text .= " %9`-[enable]->%9 ".$site{'enable'}."\n"; } print CLIENTCRAP draw_box("Newsline", $text, "newsline sources", 1); } else { foreach (@arg) { if (defined $sites{$_}) { call_openurl($sites{$_}->{'link'}) if defined $sites{$_}->{'link'}; } elsif (/\d+/ && defined $ticker[$_-1]) { call_openurl($ticker[$_-1]->{'link'}); } } } } sub show_ticker (@) { my (@ticker) = @_; my $i = 1; my $text = ''; foreach (@ticker) { my $space = ' 'x(length(scalar(@ticker))-length($i)); my $newsitem = '%r'.$space.$i.'->%n['.$$_{source}.'] %9'.$$_{title}.'%9'; $newsitem .= ' %9[*]%9' if defined($$_{description}); $text .= $newsitem."\n"; $text .= " %B`->%n%U".$$_{link}."%U \n" if Irssi::settings_get_bool('newsline_show_url'); $i++; } print CLIENTCRAP draw_box("Newsline", $text, "headlines", 1); } sub call_openurl ($) { my ($url) = @_; no strict "refs"; # check for a loaded openurl if (my $code = Irssi::Script::openurl::->can('launch_url')) { $code->($url); } else { print CLIENTCRAP "%R>>%n Please install openurl.pl"; } use strict "refs"; } sub newsline_ticker ($$) { my ($item, $get_size_only) = @_; if (Irssi::settings_get_bool('newsline_ticker_scroll')) { draw_tape($item, $get_size_only); } else { draw_ticker($item, $get_size_only); } } sub draw_ticker ($$) { my ($item, $get_size_only) = @_; if ($index >= scalar(@ticker)) { $index = 0 } my $tape; $tape .= '%F%Y%n' if $forked; if (scalar(@ticker) > 0) { my $title = $ticker[$index]->{'title'}; my $source = $ticker[$index]->{'source'}; $tape .= '>'.($index+1).': ['.$source.'] '.$title; $tape .= ' [*]' if defined($ticker[$index]->{description}); $tape .= '<'; } else { $tape .= '>Enter "/newsline fetch" to retrieve tickerdata>' unless $forked; } $tape = substr($tape, 0, Irssi::settings_get_int('newsline_ticker_max_width')); my $format = "{sb ".$tape."}"; $item->{min_size} = $item->{max_size} = length($tape)+2; $item->default_handler($get_size_only, $format, 0, 1); } sub rotate ($$) { my ($text, $rot) = @_; return($text) if length($text) < 1; for (0..$rot) { my $letter = substr($text, 0, 1); $text = substr($text, 1); $text = $text.$letter; } return($text); } sub draw_tape ($$) { my ($item, $get_size_only) = @_; my $tape; if (scalar(@ticker) > 0) { my $i=1; foreach (@ticker) { my $title = $_->{'title'}; my $source = $_->{'source'}; $tape .= '>'.($i).': ['.$source.'] '.$title.'|'; $i++; } $tape = $tape; $slide = 0 if $slide >= length($tape); $tape = rotate($tape, $slide); $tape = substr($tape, 0, Irssi::settings_get_int('newsline_ticker_max_width')); } else { $tape .= 'Use "/newsline -f" to fetch tickerdata'; } my $format = "{sb ".$tape."}"; $item->{min_size} = $item->{max_size} = length($tape)+2; $item->default_handler($get_size_only, $format, 0, 1); } sub cycle_ticker () { $index++; if ($index >= scalar(@ticker)) { $index = 0 } $slide++; Irssi::statusbar_items_redraw('newsline_ticker'); } sub update_ticker () { fork_get(); } sub reload_config() { my $filename = Irssi::settings_get_str('newsline_sites_file'); my $text; if (-e $filename) { local *F; open F, "<",$filename; $text .= $_ foreach (); close F; if ($text) { no strict; my %pages = %{ eval "$text" }; if (%pages) { %sites = (); foreach (keys %pages) { $sites{$_} = $pages{$_}; } } } } Irssi::timeout_remove($timer_cycle) if defined $timer_cycle; Irssi::timeout_remove($timer_update) if defined $timer_update; $timer_cycle = Irssi::timeout_add(Irssi::settings_get_int('newsline_ticker_cycle_delay'), 'cycle_ticker', undef) if Irssi::settings_get_int('newsline_ticker_cycle_delay') > 0; $timer_update = Irssi::timeout_add(Irssi::settings_get_int('newsline_fetch_interval')*1000, 'update_ticker', undef) if Irssi::settings_get_int('newsline_fetch_interval') > 0; Irssi::statusbar_items_redraw('newsline_ticker'); print CLIENTCRAP '%R>>%n Newsline sites loaded from '.$filename; } sub save_config() { local *F; my $filename = Irssi::settings_get_str('newsline_sites_file'); open(F, '>',$filename); my $dumper = Data::Dumper->new([\%sites], ['sites']); $dumper->Purity(1)->Deepcopy(1); my $data = $dumper->Dump; print (F $data); close(F); print CLIENTCRAP '%R>>%n Newsline sites saved to '.$filename; } sub de_umlaut ($) { my ($data) = @_; Unicode::String->stringify_as('utf8'); my $s = new Unicode::String($data); my $result = $s->latin1(); return($result); } sub sig_complete_word ($$$$$) { my ($list, $window, $word, $linestart, $want_space) = @_; return unless $linestart =~ /^.newsline (toggle|delete|add|limit)/; foreach (keys %sites) { push @$list, $_ if /^(\Q$word\E.*)?$/; } Irssi::signal_stop(); } Irssi::signal_add_first('complete word', \&sig_complete_word); Irssi::signal_add('setup saved', \&save_config); Irssi::command_bind('newsline', \&cmd_newsline); foreach my $cmd ('description', 'paste', 'paste description', 'fetch', 'reload', 'save', 'list', 'toggle', 'add', 'delete', 'help', 'limit') { Irssi::command_bind('newsline '.$cmd => sub { cmd_newsline("$cmd ".$_[0], $_[1], $_[2]); } ); } Irssi::settings_add_int($IRSSI{'name'}, 'newsline_fetch_interval', 600); Irssi::settings_add_int($IRSSI{'name'}, 'newsline_ticker_max_width', 50); Irssi::settings_add_int($IRSSI{'name'}, 'newsline_ticker_cycle_delay', 3000); Irssi::settings_add_str($IRSSI{'name'}, 'newsline_sites_file', Irssi::get_irssi_dir()."/newsline_sites"); Irssi::settings_add_bool($IRSSI{'name'}, 'newsline_show_url', 1); Irssi::settings_add_bool($IRSSI{'name'}, 'newsline_ticker_scroll', 0); Irssi::statusbar_item_register('newsline_ticker', 0, 'newsline_ticker'); reload_config(); update_ticker(); print CLIENTCRAP '%B>>%n '.$IRSSI{name}.' '.$VERSION.' loaded: /newsline help for help';