1use strict;
2use vars qw($VERSION %IRSSI %HELP);
3
4use Irssi 0 qw
5(
6   active_win server_find_tag signal_stop window_find_name parse_special
7);
8
9use Irssi::UI;
10use Irssi::TextUI;
11
12$VERSION = '1.1';
13%IRSSI =
14(
15   'authors'      => 'Marcin Rozycki',
16   'contact'      => 'derwan@irssi.pl',
17   'name'         => 'paste',
18   'description'  => 'Pasting lines to specified targets, type "/paste -help" for help',
19   'license'      => 'GNU GPL v2',
20   'modules'      => '',
21   'url'          => 'http://derwan.irssi.pl',
22   'changed'      => '2018-07-14',
23);
24
25$HELP{'paste'} = <<EOF;
26PASTE [-help] [-c] [-q] [-msg | -notice] [-<server tag>] [<target>] [<indexes>]
27
28    -help: print this help
29    -c: enable colors
30    -q: quiet mode (pasted lines are not dispalyed)
31    -msg: sends messages as msg (as default)
32    -notice: sends messages as notice
33    -<server target>: sends messages to specified server
34    <target>: targets (separated with commas)
35    <indexes>: indexes of lines to paste ( separated with spaces)
36
37Examples:
38
39    /PASTE                           - pasting to active channel or query
40    /PASTE -c                        - pasting to active channel or query with colors
41    /PASTE -c 1 3-5                  - pasting to active channel or query lines 1, 3, 4 and 5
42    /PASTE -notice                   - pasting to active item - messages sent as notice, not msg
43    /PASTE derwan                    - sends messages to derwan
44    /PASTE -ircnet -c derwan,#irssi  - sends messages (with colors) to derwan and #irssi in IRCNet
45
46Paste window - indexes:
47
48    [0] [<index>] [<index from>-<index to>]
49
50Examples:
51
52    0          - cancel
53    4          - line 4
54    4 8 9 10   - lines 4, 8, 9, 10
55    4 8-10     - lines 4, 8, 9, 10
56
57Themes:
58
59    paste_normal             - \$0 line
60    paste_reverse            - \$0 line
61    paste_count              - \$0 count, \$1 server tag, \$2 target
62    paste_input
63    paste_no_server          - \$0 comment
64    paste_argument_missing   - \$0 option, \$1 comment
65    paste_argument_unknown   - \$0 option, \$1 comment
66    paste_nothing
67
68Your version is $VERSION - for updates visit $IRSSI{url}
69Mail bug reports and suggestions to <$IRSSI{contact}>
70EOF
71
72my ( $p );
73
74# paste (str data, rec server, rec window)
75sub paste ($$$)
76{
77    buf_destroy();
78
79    $p = {};
80    $p->{color} = 0;
81    $p->{cmd} = 'msg';
82    $p->{quiet} = 0;
83
84    my $win = active_win();
85
86    foreach my $arg ( split /\s+/, $_[0] )
87    {
88       ( $arg eq '-help' ) and Irssi::print($HELP{'paste'}, MSGLEVEL_CLIENTCRAP), return;
89       ( $arg eq '-c' ) and $p->{color} = 1, next;
90       ( $arg eq '-q' ) and $p->{quiet} = 1, next;
91       ( $arg =~ m/^-(msg|notice)$/ ) and $p->{cmd} = $1, next;
92       ( $arg =~ m/^-(.*)$/ and !$p->{tag} ) and $p->{tag} = $1, next;
93       ( $arg =~ m/^([^-\s]*[^-\d]+[^\s]*)$/ and !$p->{target} ) and $p->{target} = $1, next;
94       ( $arg =~ m/^([1-9]\d*)$/ ) and $p->{l}->{$1} = 1, next;
95       if ( $arg =~ m/^([1-9]\d*)-(\d+)$/ and $1 <= $2 )
96       {
97          map { $p->{l}->{$_} = $p->{buf}->[$_-1] } ( $1 .. $2 );
98          next;
99       }
100
101       $win->printformat
102       (
103          MSGLEVEL_CRAP, 'paste_argument_unknown', $arg, 'type /paste -help for help'
104       );
105       buf_destroy(), return;
106    }
107
108    if ( !exists $p->{tag} or !defined $p->{tag} )
109    {
110       if ( !ref $_[1] and !ref $win->{server} )
111       {
112           $win->printformat
113	   (
114	      MSGLEVEL_CRAP, 'paste_argument_missing', 'server tag', 'type /paste -help for help'
115	   );
116           buf_destroy(), return;
117       }
118       $p->{tag} = ( ref $_[1] ) ? $_[1]->{tag} : $win->{active}->{server}->{tag};
119    }
120    elsif ( ! ref server_find_tag($p->{tag}) )
121    {
122       $win->printformat
123       (
124          MSGLEVEL_CRAP, 'paste_argument_unknown', $p->{tag}, 'not connected to that server'
125       );
126       buf_destroy(), return;
127    }
128
129    unless ( exists $p->{target} and defined $p->{target} )
130    {
131       if ( !ref $win->{active} or !$win->{active}->{name} )
132       {
133          $win->printformat
134	  (
135	     MSGLEVEL_CRAP, 'paste_argument_missing', 'target', 'type /paste -help for help'
136          );
137          buf_destroy(), return;
138       }
139       $p->{target} = $win->{active}->{name};
140    }
141
142    if ( buf_create() == 0 )
143    {
144       $win->printformat
145       (
146          MSGLEVEL_CRAP, 'paste_nothing'
147       );
148       buf_destroy(), return;
149    }
150
151    foreach my $idx ( keys %{$p->{l}} )
152    {
153       $p->{l}->{$idx} = $p->{buf}->[$idx-1];
154    }
155
156    buf_destroy(), return if ( buf_flush() != 0 );
157
158    $p->{win} = sprintf('paste.%d', (int(rand(9000))+1000));
159    my $input = Irssi::Windowitem::window_create($p->{win}, 1);
160    $input->set_name($p->{win});
161    $input->set_history($p->{win});
162    $input->change_server(server_find_tag($p->{tag}));
163
164    my $width = $input->{width} - 8;
165    my $theme = 'normal';
166
167    for ( my $idx = $#{$p->{buf}}; $idx >= 0; $idx-- )
168    {
169      my $text = $p->{buf}->[$idx]->get_text(0);
170      $text = sprintf
171      (
172         '%03d %'.( length($text) > $width ? '.'.($width-1).'s$' : '-'.$width.'s' ).
173	 ' %03d', $idx+1, $text, $idx+1
174      );
175      $input->printformat(MSGLEVEL_NOHILIGHT, 'paste_'.$theme, $text);
176      $theme = $theme eq 'normal' ? 'reverse' : 'normal';
177    }
178
179    $input->printformat(MSGLEVEL_NOHILIGHT, 'paste_input');
180    $input->set_active();
181};
182
183sub buf_create ()
184{
185    return unless ( defined $p and ref $p );
186
187    my $win = active_win();
188    return 0 unless ( ref $win );
189
190    my $curline =  $win->view()->{buffer}->{cur_line};
191    return 0 unless ( ref $curline );
192
193    for ( my $idx = 0; $idx < 100; $idx++ )
194    {
195       last unless ( ref $curline );
196       push @{$p->{buf}}, $curline;
197       $curline = $curline->prev();
198    }
199
200    return ( $#{$p->{buf}} >= 0 ? 1 : 0 );
201}
202
203sub buf_flush ()
204{
205    return unless ( defined $p and ref $p );
206
207    my $serv = server_find_tag($p->{tag});
208
209    unless ( ref $serv and $serv->{connected} )
210    {
211       active_win()->printformat
212       (
213          MSGLEVEL_CRAP, 'paste_no_server', $p->{tag}
214       );
215
216       return -1;
217    }
218
219    my $count = 0;
220    foreach my $idx ( sort { $b <=> $a } ( keys %{$p->{l}} ) )
221    {
222       if ( defined $p->{l}->{$idx} and ref $p->{l}->{$idx} and ++$count )
223       {
224          if ( $p->{quiet} == 0 )
225	  {
226	     my $cmd = sprintf
227	     (
228	        '%s %s %s', $p->{cmd}, $p->{target}, convertstr($p->{l}->{$idx}->get_text($p->{color}))
229	     );
230             $serv->command($cmd);
231          }
232	  else
233	  {
234             my $raw = sprintf
235	     (
236                '%s %s :%s', ( $p->{cmd} eq 'msg' ? 'privmsg' : 'notice' ), $p->{target},
237		convertstr($p->{l}->{$idx}->get_text($p->{color}))
238             );
239	     $serv->send_raw($raw);
240          }
241
242       }
243    }
244
245    if ( $count > 0 )
246    {
247       active_win()->printformat(MSGLEVEL_CRAP, 'paste_count', $count, $p->{tag}, $p->{target});
248    }
249    else
250    {
251       active_win()->printformat(MSGLEVEL_CRAP, 'paste_nothing');
252    }
253
254    return $count;
255}
256
257# sig_send_command (str data, rec server, rec window)
258sub sig_send_command ($$$)
259{
260    unless ( defined $p and ref $p and defined $p->{win} )
261    {
262       return;
263    }
264
265    my $win = active_win();
266
267    if ( $_[0] eq 0 )
268    {
269       buf_destroy(), return;
270    }
271
272    if ( substr($_[0], 0, 1) eq parse_special('$K') )
273    {
274       return;
275    }
276
277    unless ( ref $win and $win->{name} eq $p->{win} )
278    {
279       return;
280    }
281
282    signal_stop ();
283
284    $win->destroy();
285    delete $p->{win};
286
287    foreach my $arg ( split /\s+/, $_[0] )
288    {
289       if ( $arg =~ m/^(\d+)$/ and $1 > 0 )
290       {
291          $p->{l}->{$1} = $p->{buf}->[$1-1];
292       }
293       elsif ( $arg =~ m/^([1-9]\d*)-(\d+)$/ and $1 <= $2 )
294       {
295          map { $p->{l}->{$_} = $p->{buf}->[$_-1] } ( $1 .. $2 );
296       }
297       else
298       {
299          active_win()->printformat(MSGLEVEL_CRAP, 'paste_argument_unknown', $arg, 'type /paste -help for help');
300       }
301    }
302
303    buf_flush();
304    buf_destroy();
305};
306
307sub buf_destroy ()
308{
309   if ( defined $p and ref $p )
310   {
311       @{$p->{buf}} = () if ( defined $p->{buf} and ref $p->{buf} );
312       %{$p->{l}} = () if ( defined $p->{l} and ref $p->{l} );
313       if ( defined $p->{win} )
314       {
315          my $win = window_find_name($p->{win});
316          $win->destroy if ( ref $win );
317       }
318       undef ( $p );
319   }
320}
321
322# convertstr (str text), str text
323# thanks for Stanislaw Halik <weirdo@blindfold.no-ip.com>
324sub convertstr ($)
325{
326   if ( $_[0] )
327   {
328      $_[0] =~ s/[\004]g\//\003\002\002/g;
329      $_[0] =~ s/[\004]\?\/+/\0030\002\002/g;
330      $_[0] =~ s/[\004]0\//\0031\002\002/g;
331      $_[0] =~ s/[\004]0/\0031\002\002/g;
332      $_[0] =~ s/[\004]1\//\0032\002\002/g;
333      $_[0] =~ s/[\004]1/\0032\002\002/g;
334      $_[0] =~ s/[\004]2\//\0033\002\002/g;
335      $_[0] =~ s/[\004]2/\0033\002\002/g;
336      $_[0] =~ s/[\004]<\//\0034\002\002/g;
337      $_[0] =~ s/[\004]</\0034\002\002/g;
338      $_[0] =~ s/[\004]4\//\0035\002\002/g;
339      $_[0] =~ s/[\004]4/\0035\002\002/g;
340      $_[0] =~ s/[\004]5\//\0036\002\002/g;
341      $_[0] =~ s/[\004]5/\0036\002\002/g;
342      $_[0] =~ s/[\004]6\//\0037\002\002/g;
343      $_[0] =~ s/[\004]6/\0037\002\002/g;
344      $_[0] =~ s/[\004]>\//\0038\002\002/g;
345      $_[0] =~ s/[\004]>/\0038\002\002/g;
346      $_[0] =~ s/[\004]:\//\0039\002\002/g;
347      $_[0] =~ s/[\004]:/\0039\002\002/g;
348      $_[0] =~ s/[\004]3\//\00310\002\002/g;
349      $_[0] =~ s/[\004]3/\00310\002\002/g;
350      $_[0] =~ s/[\004]\;\//\00311\002\002/g;
351      $_[0] =~ s/[\004]\;/\00311\002\002/g;
352      $_[0] =~ s/[\004]9\//\00312\002\002/g;
353      $_[0] =~ s/[\004]9/\00312\002\002/g;
354      $_[0] =~ s/[\004]=\//\00313\002\002/g;
355      $_[0] =~ s/[\004]=/\00313\002\002/g;
356      $_[0] =~ s/[\004]8\//\00314\002\002/g;
357      $_[0] =~ s/[\004]8/\00314\002\002/g;
358      $_[0] =~ s/[\004]7\//\00315\002\002/g;
359      $_[0] =~ s/[\004]7/\00315\002\002/g;
360      $_[0] =~ s/[\004]g\//\003\002\002/g;
361      $_[0] =~ s/[\004]g/\003\002\002/g;
362      $_[0] =~ s/[\004]8\//\003\002\002/g;
363      $_[0] =~ s/[\004]8/\003\002\002/g;
364   }
365   return $_[0];
366}
367
368Irssi::theme_register
369([
370   'paste_normal', '$0-',
371   'paste_reverse', '%c$0-%n',
372   'paste_input', '%7%r type indexes of lines to paste (type "0" for cancel or "/paste -help" for help): %8%n',
373   'paste_count', '%_Irssi%_: {hilight $0} line(s) have been pasted to {nick $2} in $1',
374   'paste_argument_unknown', '%_Irssi%_: Unknown option: {hilight $0} {comment $1}',
375   'paste_argument_missing', '%_Irssi%_: Not enough parameters given: $0 {comment $1}',
376   'paste_no_server', '%_Irssi%_: Not connected to specified server {comment $0}',
377   'paste_nothing', '%_Irssi%_: Nothing to paste',
378]);
379
380Irssi::signal_add_first('send command', 'sig_send_command');
381Irssi::command_bind('paste', 'paste');
382