1#
2# This script requires external perl module News::NNTPClient. You can download
3# sources of this module from:
4#		http://www.cpan.org/authors/id/RVA/NNTPClient-0.37.tar.gz
5#		http://derwan.irssi.pl/perl-modules/NNTPClient-0.37.tar.gz
6# Usage:
7#	/ARTICLE [-s <server>] [-p <port>] [-P <password> -U <login>] [-l <group> <count>] [-a] [-L <index>] <Message-ID>
8# Settings:
9#	/SET news_nntp_server [server] (default environment variable 'NNTPSERVER' is used (or news.tpi.pl if variable not set))
10#	/SET news_nntp_port [port] (default is 119)
11#	/SET news_show_headers [headers] (default: from newsgroups subject message-id date lines)
12#	/SET news_use_news_window [On/Off] (default is On)
13#	/SET news_show_signature [On/Off] (default is On)
14#	/SET news_use_body_colors [On/Off] (default is On)
15#	/SET news_check_count [count] (default is 5)
16#	/SET news_use_auth [On/Off] (default is Off)
17#	/SET news_auth_user [login]
18#	/SET news_auth_password [password]
19
20use strict;
21use Irssi;
22use 5.6.0;
23use POSIX;
24
25use vars qw($VERSION %IRSSI);
26$VERSION = "0.5.9";
27%IRSSI = (
28	'authors'	=> 'Marcin Rozycki, Mathieu Doidy',
29	'contact'	=> 'derwan@irssi.pl',
30	'name'		=> 'news',
31	'description'	=> 'News reader, usage:  /article [-s <server>] [-p <port>] [-P <password> -U <login>] [-l <group> <count>] [-a] [-L <index>] <message-id>',
32	'url'		=> 'http://derwan.irssi.pl',
33	'license'	=> 'GNU GPL v2',
34	'changed'	=> 'Fri Feb  6 21:26:57 CET 2004',
35);
36
37use News::NNTPClient;
38
39my $debug_level = 0;
40my $nntp_server = $ENV{'NNTPSERVER'}; $nntp_server = 'news.tpi.pl' unless $nntp_server;
41my $nntp_port = '119';
42my $default_headers = 'from newsgroups';
43my $check_count = 5;
44my %pipe_tag = ();
45my $news_window_name = 'news';
46my @colors = (15, 12, 03, 06, 05, 07, 14);
47my @articles = ();
48
49Irssi::command_bind article => sub {
50	my $usage = '/article [-s <server>] [-p <port>] [-P <password> -U <login>] [-l <group> <count>] [-a] [-L <index>] <Message-ID>';
51
52	my $window;
53	if (Irssi::settings_get_bool('news_use_news_window')) {
54		$window = Irssi::window_find_name($news_window_name);
55		if (!$window) {
56			Irssi::command('^window new hide');
57			Irssi::command('^window name '.$news_window_name);
58			$window = Irssi::window_find_name($news_window_name);
59		}
60	} else {
61		$window = Irssi::active_win();
62	}
63
64	my $server = Irssi::settings_get_str('news_nntp_server');
65	$server = $nntp_server unless $server;
66	my $port = Irssi::settings_get_int('news_nntp_port');
67	$port = $nntp_port unless ($port > 0);
68	my $count = Irssi::settings_get_int('news_check_count');
69	$count = $check_count unless ($count > 0);
70
71	my ($connection, $artid, $group, $strip, $showall, @article);
72	my ($auth, $user, $password);
73	my $yes = 0;
74
75	@_ = split(/ +/, $_[0]);
76	while ($_ = shift(@_))
77	{
78		/^-a$/ and $showall = 1, next;
79		/^-s$/ and $server = shift(@_), next;
80		/^-p$/ and $port = shift(@_), next;
81		/^-P$/ and $password = shift(@_), next;
82		/^-U$/ and $user = shift(@_), next;
83		/^-l$/ and do {
84			$group = shift(@_);
85			$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_group_missing', $usage), return unless ($group);
86			$_ = shift(@_);
87			$count = $_, next if ($_ =~ /^\d+$/ and $_ > 0);
88		};
89		/^-yes$/i and ++$yes, next;
90		/^-L$/ and do {
91			$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_no_artids'), return if ($#articles < 0);
92			if ($artid = shift(@_)) {
93				$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_unknown_argument', $artid, $usage), return if ($artid !~ /^\d+/ or $artid < 0 or $artid > 10);
94				$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_unknown_artid', ++$artid), return unless ($articles[--$artid]);
95			    $_ = $articles[$artid]->[0];
96            } else {
97				for (my $idx = 0; $idx <= $#articles; $idx++) {
98					$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_artid_show',
99						($idx + 1), $articles[$idx]->[0], $articles[$idx]->[1], $articles[$idx]->[2]);
100				}
101			    return;
102            }
103		};
104		/^-/ and $window->printformat(MSGLEVEL_CLIENTCRAP, 'news_unknown_argument', $_, $usage), return;
105		$artid = ($_ =~ /^<.*>$/) ? $_ : '<'.$_.'>';
106		last;
107	}
108
109	$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_server_unknown', $server), return if (!$server or $server !~ /^..*\...*/);
110	$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_port_unknown', $port), return if (!$port or $port !~ /^\d+$/ or $port == 0 or $port > 65535);
111	$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_missing_argument', $usage), return if (!$group and !$artid);
112	$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_article_unknown', $artid), return if (!$group and $artid !~ /^<..*\@..*>$/);
113
114	my ($rh, $wh);
115	pipe($rh, $wh);
116
117	my $pid = fork();
118	unless (defined $pid) {
119		close($rh); close($wh);
120		$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_cannot_fork');
121		return;
122
123	} elsif ($pid) {
124		close ($wh);
125		$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_server_connecting', $server, $port, $artid);
126		Irssi::pidwait_add($pid);
127		$pipe_tag{$rh} = Irssi::input_add(fileno($rh), INPUT_READ, \&news_fork, $rh);
128		return;
129	}
130
131	close($rh);
132
133	$connection = new News::NNTPClient($server, $port, $debug_level);
134	print($wh "not_connected $server $port\n"), goto END unless ($connection->{CODE} =~ /^(200|201)$/);
135	print($wh "connected ".$connection->{MESG}."\n");
136
137        if ($user && $password or Irssi::settings_get_bool('news_use_auth')) {
138		$user = Irssi::settings_get_str('news_auth_user') unless defined $user;
139		$password = Irssi::settings_get_str('news_auth_password') unless defined $password;
140		$connection->authinfo($user,$password);
141	}
142
143
144	if ($group) {
145		print($wh "listgroup_yes $count\n"), goto END if ($count > 10 and !$yes);
146		print($wh "listgroup_request $server $group\n");
147		my @list = $connection->listgroup($group);
148		print($wh "listgroup_error $server ".$connection->{MESG}."\n"), goto END if ($#list < 0);
149
150		my $num = $#list;
151		print($wh "listgroup_num $group $num $count\n");
152		@list = @list[($num - $count) .. $num] if ($num > --$count);
153
154		N: while ($_ = shift(@list))
155		{
156			chomp;
157			print($wh "space\n");
158			foreach my $xhdr ("From", "Subject", "Message-ID")
159			{
160				my @reply = $connection->xhdr($xhdr, $_);
161				chomp $reply[0]; $reply[0] =~ s/^\d+ //;
162				goto N unless ($reply[0]);
163				if ($xhdr eq "Message-ID") {
164					print($wh "memo $reply[0]\,news.pl: listgroup,$group\n");
165				} elsif ($xhdr eq "From") {
166					$reply[0] = "\002" . $reply[0] . "\002";
167				}
168				print($wh "listgroup_header $xhdr $reply[0]\n");
169			}
170		}
171		print($wh "space\n");
172
173	} else {
174		my $show_headers = $default_headers .' '. Irssi::settings_get_str('news_show_headers');
175		my ($head, $idx, $usecolors, $bodycolor) = (1, 0, Irssi::settings_get_bool('news_use_body_colors'), $colors[0]);
176
177		print($wh "article_request $server $artid\n");
178		foreach ($connection->article($artid))
179		{
180			unless ($idx++) {
181				print($wh "space\n");
182				print($wh "memo $artid\,news.pl: read,$server\n");
183			}
184			chomp; s/\t/        /g;
185			/^-- / and do {
186				last if (!$showall and !Irssi::settings_get_bool('news_show_signature'));
187				$bodycolor = $colors[6], $usecolors = 0 if $usecolors;
188			};
189			unless ($head) {
190				/^$/ and next if (!$showall and $strip++);
191				/^..*$/ and $strip = 0;
192				if ($usecolors) {
193					$_ =~ /^[>| ]+/;
194					my $prefix = $&; $prefix =~ s/ //g;
195					$bodycolor = ($_ =~ /^[>]+/) ? $colors[(((length($prefix)-1) %5)+1)] : $colors[0];
196				}
197				print($wh "article_body \003$bodycolor$_\n");
198				next;
199			}
200			/^$/ and print($wh "space\n"), $head = 0, next;
201			my ($header, $text) = split(/: /, $_, 2);
202			print($wh "article_header $header $text\n") if ($showall or $show_headers =~ /\b$header\b/i);
203		}
204		print($wh "article_notexist $server $artid\n") unless ($idx);
205		print($wh "space\n") unless ($strip);
206	}
207
208	END: print($wh "close\n");
209	close($wh);
210	POSIX::_exit(1);
211};
212
213sub memo {
214	my ($text, $who, $where) = @_;
215	G: while ($text =~ /<[A-Za-z0-9\S]+\@[A-Za-z0-9\S]+>/g)
216	{
217		my $artid = $&;
218		foreach my $array (@articles) { goto G if ($artid eq $array->[0]); }
219		unshift @articles, [$artid, $who, $where];
220	}
221	$#articles = 9 if ($#articles > 9);
222}
223
224sub news_fork {
225	my $rh = shift;
226	while (<$rh>)
227	{
228		chomp;
229		/^close/ and last;
230		/^memo / and memo(split(",", $', 3)), next;
231		my ($theme, @args) = split / +/, $_, 5;
232		my $window = Irssi::window_find_name($news_window_name);
233		$window = Irssi::active_win() unless $window;
234		$window->printformat(MSGLEVEL_CLIENTCRAP, 'news_' . $theme, @args);
235	}
236
237	Irssi::input_remove($pipe_tag{$rh});
238	close($rh);
239}
240
241Irssi::signal_add_last 'message private' => sub { memo($_[1], $_[2], $_[3]); };
242Irssi::signal_add_last 'message public' => sub { memo($_[1], $_[2], $_[4]); };
243Irssi::signal_add_last 'dcc chat message' => sub { memo($_[1], $_[0]->{nick}, "chat"); };
244
245Irssi::theme_register([
246	'news_server_unknown',		'NNTP %_server unknown%_ or not defined, use: /set news_nntp_server [server], to set it',
247	'news_server_bad',		'%_Bad%_ NNTP server {hilight $0} (bad hostname or addres)',
248	'news_port_unknown',		'%_NNTP port%_ unknown or not defined, use: /set news_nntp_port [port], to set it',
249	'news_missing_argument',	'Missing argument, usage: $0-',
250	'news_unknown_argument',	'Unknown argument \'$0\', usage: $1-',
251	'news_server_connecting',	'Connecting to {hilight $0} on port {hilight $1}, wait...',
252	'news_not_connected',		'%_Cannot connect%_ to NNTP server $0 on port $1',
253	'news_connected',		'%_Connected%_; $0-',
254	'news_article_unknown',		'Unknown message-id {hilight $0}',
255	'news_article_notexist',	'No article {hilight $1} on $0',
256	'news_article_request',		'Sending query about article {hilight $1} to $0, wait...',
257	'news_article_body',		'$0-',
258	'news_article_header',		'%c$0:%n %_$1-%_',
259	'news_group_missing',		'Missing argument: newsgroup, usage: $0-',
260	'news_listgroup_request',	'Looking for %_new articles%_ in {hilight $1}, wait...',
261	'news_listgroup_error',		'Listgroup result: $1-',
262	'news_listgroup_num',		'$1 articles in group $0; fetching headers (max in $2 articles), wait...',
263	'news_listgroup_header',	'%c$0:%n $1-',
264	'news_listgroup_yes',		'Count > 10 ($0). Doing this is not a good idea. Add -YES option to command if you really mean it',
265	'news_no_artids',		'Sorry, list of logged message-id\'s is empty :/',
266	'news_cannot_fork',		'Cannot fork process',
267	'news_artid_show',		'[%_$[!-2]0%_] article %c$1%n [by {hilight $2} ($3-)]',
268	'news_unknown_artid',		'Article {hilight $0} not found, type /article -L, to displays list of logged message-id\'s',
269	'news_space',				' '
270]);
271
272# registering settings
273Irssi::settings_add_bool('misc', 'news_use_news_window', 1);
274Irssi::settings_add_str('misc', 'news_nntp_server', $nntp_server);
275Irssi::settings_add_int('misc', 'news_nntp_port', $nntp_port);
276Irssi::settings_add_str('misc', 'news_show_headers', $default_headers.' subject message-id date lines content-transfer-encoding');
277Irssi::settings_add_bool('misc', 'news_show_signature', 1);
278Irssi::settings_add_bool('misc', 'news_use_body_colors', 1);
279Irssi::settings_add_bool('misc', 'news_use_auth', 0);
280Irssi::settings_add_str('misc', 'news_auth_user', '');
281Irssi::settings_add_str('misc', 'news_auth_password', '');
282Irssi::settings_add_int('misc', 'news_check_count', $check_count);
283