| 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 |  |  |  |  |  |  |  |