summaryrefslogtreecommitdiffstats
path: root/scripts/newsline.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/newsline.pl')
-rw-r--r--scripts/newsline.pl453
1 files changed, 453 insertions, 0 deletions
diff --git a/scripts/newsline.pl b/scripts/newsline.pl
new file mode 100644
index 0000000..b37cfcc
--- /dev/null
+++ b/scripts/newsline.pl
@@ -0,0 +1,453 @@
+# by Stefan "tommie" Tomanek
+#
+use strict;
+
+use vars qw($VERSION %IRSSI);
+$VERSION = '2017040101';
+%IRSSI = (
+ authors => 'Stefan \'tommie\' Tomanek',
+ contact => 'stefan@pico.ruhr.de',
+ 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 <number>
+ Open the entry indicated by <number> via openurl.
+ Openurl.pl is available at http://irssi.org/scripts/.
+/newsline description <number>
+ Display a brief summary of the article if available
+/newsline paste <number>
+ 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 <Source>
+ Enable or disable the source
+/newsline add <name> <url-to-rdf>
+ 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<tab>', $filter);
+ $desc =~ s/<tab>/ /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<Fetching>%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 (<F>);
+ 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';