2089ce82cfca3aca1d97cf49881d35f0bf3be622
[xmlrtorrent.git] / xmlrtorrent.pl
1 # control an rTorrent client via XMLRPC,
2 # and collect rtorrent files from IRC for later download
3 #
4 # (c) 2007-2008 by Ralf Ertzinger <ralf@camperquake.de>
5 # licensed under GNU GPL v2
6
7 use strict;
8 use Irssi 20020324 qw (command_bind command_runsub signal_add_first signal_add_last);
9 use vars qw($VERSION %IRSSI);
10 use XML::Simple;
11 use Data::Dumper;
12 use File::Spec;
13 use xmlrtorrent;
14
15 my $conf;
16 my $conffile = File::Spec->catfile(Irssi::get_irssi_dir(), 'xmlrtorrent.xml');
17 my $scriptdir = File::Spec->catfile(Irssi::get_irssi_dir(), 'scripts');
18 my %torrentlist = ();
19 my $torrentindex = 1;
20 my $rtorrent;
21
22 my @outputstack = (undef);
23
24 my $PARAMS = {
25     'XMLURL' => 'http://localhost/RPC2',
26 };
27
28 # activate debug here
29 my $debug = 0;
30
31 # "message public", SERVER_REC, char *msg, char *nick, char *address, char *target
32 signal_add_last("message public" => sub {check_for_link(\@_,1,4,2,0);});
33 # "message own_public", SERVER_REC, char *msg, char *target
34 signal_add_last("message own_public" => sub {check_for_link(\@_,1,2,-1,0);});
35
36 # "message private", SERVER_REC, char *msg, char *nick, char *address
37 signal_add_last("message private" => sub {check_for_link(\@_,1,-1,2,0);});
38 # "message own_private", SERVER_REC, char *msg, char *target, char *orig_target
39 signal_add_last("message own_private" => sub {check_for_link(\@_,1,2,-1,0);});
40
41 # "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target
42 signal_add_last("message irc action" => sub {check_for_link(\@_,1,4,2,0);});
43 # "message irc own_action", SERVER_REC, char *msg, char *target
44 signal_add_last("message irc own_action" => sub {check_for_link(\@_,1,2,-1,0);});
45
46 # For tab completion
47 signal_add_first('complete word', \&sig_complete);
48
49 my $xmlrtorrent_commands = {
50     'save' => sub {
51         cmd_save();
52     },
53
54     'set' => sub {
55         cmd_set(@_);
56     },
57     
58     'show' => sub {
59         cmd_show(@_);
60     },
61
62     'help' => sub {
63         cmd_help(@_);
64     },
65
66     'queue' => sub {
67         cmd_queue(@_);
68     },
69
70     'remote' => sub {
71         cmd_remote(@_);
72     },
73
74     'debug' => sub {
75         $debug = 1;
76         write_irssi('Enabled debugging');
77     },
78
79     'nodebug' => sub {
80         $debug = 0;
81         write_irssi('Disabled debugging');
82     },
83 };
84
85 sub write_irssi {
86     my @text = @_;
87     my $output = $outputstack[0];
88
89     $text[0] = 'xmlrtorrent: ' . $text[0];
90
91     if (defined($output) and ref($output)) {
92         $output->print(sprintf(shift(@text), @text), MSGLEVEL_CLIENTCRAP);
93     } else {
94         Irssi::print(sprintf(shift(@text), @text));
95     }
96
97 }
98
99 sub push_output {
100     unshift(@outputstack, shift);
101 }
102
103 sub pop_output {
104     shift(@outputstack);
105 }
106
107 sub write_debug {
108     if ($debug) {
109         write_irssi(@_);
110     }
111 }
112
113 # This is shamelessly stolen from pythons urlgrabber
114 sub format_number {
115     my $number = shift;
116     my $SI = shift || 0;
117     my @symbols = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y');
118     my $step = $SI?1000:1024;
119     my $thresh = 999;
120     my $depth = 0;
121     my $max_depth = $#symbols;
122     my $format;
123
124     while (($number > $thresh) and ($depth < $max_depth)) {
125         $depth += 1;
126         $number /= $step;
127     }
128
129     if ($number =~ /^[+-]?\d+$/) {
130         # Integer.
131         $format = '%i%s';
132     } elsif ($number < 9.95) {
133         $format = '%.1f%s';
134     } else {
135         $format = '%.0f%s';
136     }
137     return sprintf($format, $number, $symbols[$depth]);
138 }
139
140
141
142 sub check_for_link {
143     my ($signal,$parammessage,$paramchannel,$paramnick,$paramserver) = @_;
144     my $server = $signal->[$paramserver];
145     my $target = $signal->[$paramchannel];
146     my $message = ($parammessage == -1) ? '' : $signal->[$parammessage];
147     my $nick = ($paramnick == -1)?defined($server)?$server->{'nick'}:'':$signal->[$paramnick];
148     my $g;
149     my $m;
150     my $p;
151
152     my $witem;
153     if (defined $server) {
154         $witem = $server->window_item_find($target);
155     } else {
156         $witem = Irssi::window_item_find($target);
157     }
158
159     # Look if we should ignore this line
160     if ($message =~ m,(?:\s|^)/nosave(?:\s|$),) {
161         return;
162     }
163
164     push_output($witem);
165
166     # Look if there is a torrent link in there
167     $message =~ m|(http://\S*\.torrent)|;
168     $m = $1;
169     while (defined($m)) {
170         write_debug('Torrent-URL: %s', $m);
171         $torrentlist{$torrentindex++} = {'CHANNEL' => $target, 'NICK' => $nick, 'URL' => $m};
172
173         # Remove the matched part from the message and try again (there may be
174         # more!)
175         $message =~ s/$m//;
176
177         $message =~ m|(http://.*\.torrent)|;
178         $m = $1;
179     }
180
181     pop_output();
182 }
183
184 # Handle the queue of unhandled torrents
185 sub cmd_queue {
186     my ($subcmd, $id, @params) = @_;
187
188     if ('remove' eq $subcmd) {
189         if (defined($id)) {
190             delete($torrentlist{$id});
191         }
192     } elsif ('clear' eq $subcmd) {
193         %torrentlist = ();
194     } elsif ('confirm' eq $subcmd) {
195         my $u;
196         return unless(defined($id) and exists($torrentlist{$id}));
197
198         $u = $torrentlist{$id}->{'URL'};
199
200         write_debug('Sending %s to rtorrent', $u);
201         unless(defined($rtorrent->load_start($u))) {
202             write_irssi('Error sending URL %s: %s', $u, $rtorrent->errstr());
203         } else {
204             delete($torrentlist{$id});
205         }
206     } elsif (('list' eq $subcmd) or !defined($subcmd))  {
207         write_irssi('List of queued torrents');
208         foreach (sort(keys(%torrentlist))) {
209             write_irssi('  %d: %s@%s: %s', $_,
210                     $torrentlist{$_}->{'NICK'},
211                     $torrentlist{$_}->{'CHANNEL'},
212                     $torrentlist{$_}->{'URL'});
213         }
214     }
215 }
216
217 # Handle the remote rtorrent queue
218 sub cmd_remote {
219     my ($subcmd, $id, @params) = @_;
220     my $rqueue;
221
222     if ('queue' eq $subcmd) {
223         unless(defined($rqueue = $rtorrent->download_list())) {
224             write_irssi('Error getting list of downloads: %s', $rtorrent->errstr());
225             return;
226         }
227
228         foreach (@{$rqueue}) {
229             write_irssi('%s%s: %sB/%sB done, %sb/s up, %sb/s down',
230                     $_->[6]?'*':' ',
231                     $_->[0],
232                     format_number($_->[2]),
233                     format_number($_->[1]),
234                     format_number($_->[3]),
235                     format_number($_->[4]));
236         }
237     }
238 }
239
240
241 sub cmd_save {
242
243     eval {
244         open(CONF, '>'.$conffile) or die 'Could not open config file';
245         print CONF XML::Simple::XMLout($conf, KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'});
246         close(CONF);
247     };
248     if ($@) {
249         write_irssi('Could not save config to %s: %s', ($conffile, $@));
250     } else {
251         write_irssi('configuration saved to %s', $conffile);
252     }
253 }
254
255 sub cmd_set {
256     my $target = shift;
257     my $key = shift;
258     my $val = shift;
259
260     if ('global' eq $target) {
261         if(exists($PARAMS->{$key})) {
262             $conf->{'xmlrtorrent'}->{$key} = $val;
263             if ('XMLURL' eq $key) {
264                 unless(defined($rtorrent = xmlrtorrent->new('XMLURL' => $conf->{'xmlrtorrent'}->{'XMLURL'}))) {
265                     write_irssi('Could not initialize XMLRPC instance');
266                     return;
267                 }
268             }
269         } else {
270             write_irssi('Key %s does not exist', $key);
271         }
272     }
273 }
274
275
276 sub cmd_show {
277     my $target = shift;
278     my $p;
279     my $e;
280 }
281
282 sub cmd_help {
283     my $target = shift;
284     my $p;
285
286     write_irssi(<<'EOT');
287 Supported commands:
288  save: Save the current configuration
289  help: Display this help
290  debug: enable debugging messages
291  nodebug: disable debugging messages
292 EOT
293 }
294
295
296 # save on unload
297 sub sig_command_script_unload {
298     my $script = shift;
299     if ($script =~ /(.*\/)?xmlrtorrent(\.pl)?$/) {
300         cmd_save();
301     }
302 }
303
304 sub init_xmlrtorrent {
305
306     my $bindings = shift;
307     my $p;
308
309     unless(-r $conffile && defined($conf = XML::Simple::XMLin($conffile, ForceArray => ['config', 'option'], KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'}))) {
310         # No config, start with an empty one
311         write_debug('No config found, using defaults');
312         $conf = { 'xmlrtorrent' => { }};
313     }
314     foreach (keys(%{$PARAMS})) {
315         unless (exists($conf->{'xmlrtorrent'}->{$_})) {
316             $conf->{'xmlrtorrent'}->{$_} = $PARAMS->{$_};
317         }
318     }
319
320     unless(defined($rtorrent = xmlrtorrent->new('XMLURL' => $conf->{'xmlrtorrent'}->{'XMLURL'}))) {
321         write_irssi('Could not initialize XMLRPC instance');
322         return;
323     }
324
325     if ($bindings) {
326
327         Irssi::signal_add_first('command script load', 'sig_command_script_unload');
328         Irssi::signal_add_first('command script unload', 'sig_command_script_unload');
329         Irssi::signal_add('setup saved', 'cmd_save');
330
331
332         Irssi::command_bind('torrent' => \&cmdhandler);
333     }
334
335     write_irssi('xmlrtorrent initialized');
336 }
337
338 sub sig_complete {
339     my ($complist, $window, $word, $linestart, $want_space) = @_;
340     my @matches;
341
342     if ($linestart !~ m|^/torrent\b|) {
343         return;
344     }
345
346     ${$want_space} = 0;
347
348     Irssi::signal_stop();
349 }
350
351 sub cmdhandler {
352     my ($data, $server, $witem) = @_;
353     my ($cmd, @params) = split(/\s+/, $data);
354
355     push_output($witem);
356
357     if (exists($xmlrtorrent_commands->{$cmd})) {
358         $xmlrtorrent_commands->{$cmd}->(@params);
359     }
360
361     pop_output();
362 }
363
364 unshift(@INC, $scriptdir);
365 init_xmlrtorrent(1);