File Coverage

blib/lib/IRC/Toolkit/CTCP.pm
Criterion Covered Total %
statement 48 56 85.7
branch 20 38 52.6
condition 3 6 50.0
subroutine 8 8 100.0
pod 3 3 100.0
total 82 111 73.8


line stmt bran cond sub pod time code
1             package IRC::Toolkit::CTCP;
2             $IRC::Toolkit::CTCP::VERSION = '0.092002';
3 2     2   859 use strictures 2;
  2         1110  
  2         64  
4 2     2   264 use Carp 'confess';
  2         2  
  2         79  
5              
6 2     2   368 use parent 'Exporter::Tiny';
  2         219  
  2         7  
7             our @EXPORT = qw/
8             ctcp_quote
9             ctcp_unquote
10             ctcp_extract
11             /;
12              
13 2     2   3923 use IRC::Message::Object 'ircmsg';
  2         6  
  2         17  
14              
15 2     2   392 use Scalar::Util 'blessed';
  2         3  
  2         1384  
16              
17             my %quote = (
18             "\012" => 'n',
19             "\015" => 'r',
20             "\0" => '0',
21             "\cP" => "\cP",
22             );
23             my %dequote = reverse %quote;
24              
25             ## CTCP handling logic borrowed from POE::Filter::IRC::Compat / Net::IRC
26             ## (by BinGOs, fimm, Abigail et al)
27              
28             sub ctcp_quote {
29 1     1 1 1860 my ($line) = @_;
30 1 50       4 confess "Expected a line" unless defined $line;
31              
32 1 50       3 if ($line =~ tr/[\012\015\0\cP]//) {
33 0         0 $line =~ s/([\012\015\0\cP])/\cP$quote{$1}/g;
34             }
35              
36 1         2 $line =~ s/\001/\\a/g;
37 1         4 "\001$line\001"
38             }
39              
40             sub ctcp_unquote {
41 5     5 1 784 my ($line) = @_;
42 5 50       13 confess "Expected a line" unless defined $line;
43              
44 5 50       13 if ($line =~ tr/\cP//) {
45 0         0 $line =~ s/\cP([nr0\cP])/$dequote{$1}/g;
46             }
47              
48 5 50       12 substr $line, rindex($line, "\001"), 1, '\\a'
49             if ($line =~ tr/\001//) % 2 != 0;
50 5 100       13 return unless $line =~ tr/\001//;
51              
52 2         5 my @chunks = split /\001/, $line;
53 2 50       5 shift @chunks unless length $chunks[0];
54             ## De-quote / convert escapes
55 2         5 s/\\([^\\a])/$1/g, s/\\\\/\\/g, s/\\a/\001/g for @chunks;
56              
57 2         3 my (@ctcp, @text);
58              
59             ## If we start with a ctrl+A, the first chunk is CTCP:
60 2 50       5 if (index($line, "\001") == 0) {
61 2         2 push @ctcp, shift @chunks;
62             }
63             ## Otherwise we start with text and alternate CTCP:
64 2         5 while (@chunks) {
65 0         0 push @text, shift @chunks;
66 0 0       0 push @ctcp, shift @chunks if @chunks;
67             }
68              
69 2         6 +{ ctcp => \@ctcp, text => \@text }
70             }
71              
72             sub ctcp_extract {
73 4     4 1 5 my ($input) = @_;
74              
75 4 50 33     14 unless (blessed $input && $input->isa('IRC::Message::Object')) {
76 4 100       16 $input = ref $input ?
77             ircmsg(%$input) : ircmsg(raw_line => $input)
78             }
79              
80 4 100       874 my $type = uc($input->command) eq 'PRIVMSG' ? 'ctcp' : 'ctcpreply' ;
81 4         40 my $line = $input->params->[1];
82 4         468 my $unquoted = ctcp_unquote($line);
83 4 100 66     24 return unless $unquoted and @{ $unquoted->{ctcp} };
  2         7  
84              
85 2         2 my ($name, $params);
86 2         3 CTCP: for my $str ($unquoted->{ctcp}->[0]) {
87 2         7 ($name, $params) = $str =~ /^(\w+)(?: +(.*))?/;
88 2 50       4 last CTCP unless $name;
89 2         3 $name = lc $name;
90 2 50       3 if ($name eq 'dcc') {
91             ## Does no extra work to parse DCC
92             ## ... but see POE::Filter::IRC::Compat for that
93 0         0 my ($dcc_type, $dcc_params) = $params =~ /^(\w+) +(.+)/;
94 0 0       0 last CTCP unless $dcc_type;
95 0 0       0 return ircmsg(
96             ( $input->prefix ? (prefix => $input->prefix) : () ),
97             command => 'dcc_request_'.lc($dcc_type),
98             params => [
99             $input->prefix,
100             $dcc_params
101             ],
102             raw_line => $input->raw_line,
103             )
104             } else {
105 2 50       17 return ircmsg(
    50          
106             ( $input->prefix ? (prefix => $input->prefix) : () ),
107             command => $type .'_'. $name,
108             params => [
109             $input->params->[0],
110             ( defined $params ? $params : '' ),
111             ],
112             raw_line => $input->raw_line,
113             )
114             }
115             }
116            
117             undef
118 0           }
119              
120              
121             1;
122              
123             =pod
124              
125             =head1 NAME
126              
127             IRC::Toolkit::CTCP - CTCP parsing utilities
128              
129             =head1 SYNOPSIS
130              
131             ## Extract first CTCP request/reply from a message:
132             if (my $ctcp_ev = ctcp_extract( $orig_msg ) ) {
133             ## CTCP was found; $ctcp_ev is an IRC::Message::Object
134             ...
135             }
136              
137             ## Properly CTCP-quote a string:
138             my $quoted_ctcp = ctcp_quote("PING 1234");
139              
140             ## Deparse CTCP messages (including multipart):
141             if (my $ref = ctcp_unquote($raw_line)) {
142             my @ctcp = @{ $ref->{ctcp} };
143             my @txt = @{ $ref->{text} };
144             ...
145             }
146              
147             =head1 DESCRIPTION
148              
149             Utility functions useful for quoting/unquoting/extracting CTCP.
150              
151             =head2 ctcp_extract
152              
153             Takes input (in the form of an L instance,
154             a hash such as that produced by L, or a
155             raw line) and attempts to extract a valid CTCP request or reply.
156              
157             Returns an L whose C carries an
158             appropriate prefix (one of B, B, or B) prepended
159             to the CTCP command:
160              
161             ## '$ev' is your incoming or outgoing IRC::Message::Object
162             ## CTCP VERSION request:
163             $ev->command eq 'ctcp_version'
164              
165             ## Reply to CTCP VERSION:
166             $ev->command eq 'ctcpreply_version'
167              
168             ## DCC SEND:
169             $ev->command eq 'dcc_request_send'
170              
171             Returns C if no valid CTCP was found; this is a breaking change in
172             C, as previous versions returned the empty list.
173              
174             =head2 ctcp_quote
175              
176             CTCP quote a raw line.
177              
178             =head2 ctcp_unquote
179              
180             Deparses a raw line possibly containing CTCP.
181              
182             Returns a hash with two keys, B and B, whose values are
183             ARRAYs containing the CTCP and text portions of a CTCP-quoted message.
184              
185             Returns an empty list if no valid CTCP was found.
186              
187             =head1 AUTHOR
188              
189             Jon Portnoy
190              
191             Code derived from L and L,
192             copyright BinGOs, HINRIK, fimm, Abigail et al
193              
194             Licensed under the same terms as Perl.
195              
196             =cut
197