IRC::Utils(3pm)

πŸ“¦
version 0.12 Β· 4 versions
, in debian bookworm, debian trixie, ubuntu jammy, ubuntu noble

πŸ¦‹ IRC::Utils πŸ¦‹

okay bestie, so this is IRC::Utils from the User Contributed Perl Documentation β€” it's the little helper squad you call in when you're doing IRC stuff and you're tired of hand-rolling the same parsing code for the 500th time. πŸ’…


✨ NAME ✨

IRC::Utils β€” Common utilities for IRC-related tasks πŸ’–


🌸 SYNOPSIS 🌸

here's the vibe, literally just import it and go:

use strict;
use warnings;

use IRC::Utils ':ALL';

my $nickname = '^Lame|BOT[moo]';
my $uppercase_nick = uc_irc($nickname);
my $lowercase_nick = lc_irc($nickname);

print "They're equivalent\n" if eq_irc($uppercase_nick, $lowercase_nick);

my $mode_line = 'ov+b-i Bob sue stalin*!*@*';
my $hashref = parse_mode_line($mode_line);

my $banmask = 'stalin*';
my $full_banmask = normalize_mask($banmask);

if (matches_mask($full_banmask, 'stalin!joe@kremlin.ru')) {
    print "EEK!";
}

my $decoded = irc_decode($raw_irc_message);
print $decoded, "\n";

if (has_color($message)) {
   print 'COLOR CODE ALERT!\n";
}

my $results_hashref = matches_mask_array(\@masks, \@items_to_match_against);

my $nick = parse_user('stalin!joe@kremlin.ru');
my ($nick, $user, $host) = parse_user('stalin!joe@kremlin.ru');

πŸ‘‘ DESCRIPTION πŸ‘‘

the functions in this module handle sooo many of the little chores you get hit with when you're working with IRC. mode lines, ban masks, message encoding and formatting, all of it. it's giving "someone already did the homework for you". ✨


πŸ’– FUNCTIONS πŸ’–

πŸŽ€ uc_irc

takes one mandatory param (the string you want IRC-uppercased) and one optional param, which is the casemapping of the ircd. your options there are 'rfc1459', 'strict-rfc1459' or 'ascii', and the default is 'rfc1459'. returns the IRC uppercase version of the string you passed in. 🫢

πŸŽ€ lc_irc

same deal but lowercase: one mandatory param (string to IRC-lowercase) plus one optional param for the ircd casemapping β€” 'rfc1459', 'strict-rfc1459' or 'ascii', defaulting to 'rfc1459'. returns the IRC lowercase equivalent. πŸ’œ

πŸŽ€ eq_irc

takes two mandatory params, both IRC strings (channels or nicknames) that you want compared. a third optional param sets the casemapping. returns true if the two strings are equivalent, false otherwise. it's literally just the long version but shorter:

# long version
lc_irc($one, $map) eq lc_irc($two, $map)

# short version
eq_irc($one, $two, $map)

πŸŽ€ parse_mode_line

takes a list representing an IRC mode line, hands you back a hashref. and if you want, you can also hand it an arrayref and a hashref to spell out valid channel modes (default: "[qw(beI k l imnpstaqr)]") and status modes (default: "{o => '@', h => '%', v => '+'}") respectively. πŸ’…

if the modeline couldn't be parsed, the hashref just comes back empty. no drama. on success these keys are sitting in there:

  • 'modes' β€” an arrayref of normalised modes;
  • 'args' β€” an arrayref of applicable arguments to the modes.

example time:

my $hashref = parse_mode_line( 'ov+b-i', 'Bob', 'sue', 'stalin*!*@*' );

# $hashref will be:
{
   modes => [ '+o', '+v', '+b', '-i' ],
   args  => [ 'Bob', 'sue', 'stalin*!*@*' ],
}

πŸŽ€ normalize_mask

takes one param, a string that's an IRC mask. returns a normalised full mask. slay.

$fullbanmask = normalize_mask( 'stalin*' );

# $fullbanmask will be: 'stalin*!*@*';

πŸŽ€ matches_mask

takes two params: a string representing an IRC mask, and something to match against that mask (like a nick!user@hostname string). returns a true value if they match, false otherwise. optionally you can pass the casemapping (peep "uc_irc"), because this function uses "uc_irc" under the hood. πŸ”₯

πŸŽ€ matches_mask_array

takes two array refs β€” first one is a list of strings representing IRC masks, second is a list of somethings to test against those masks. if there are no matches you get an empty hashref back. otherwise the keys are the masks that got matched, and each value is an arrayref of the strings that matched it. optionally pass the casemapping (see "uc_irc"), since this one uses "uc_irc" internally too. πŸ’–

πŸŽ€ unparse_mode_line

takes one argument, a string representing a bunch of mode changes. returns a condensed version of those changes. ✨

my $mode_line = unparse_mode_line('+o+o+o-v+v');
$mode_line is now '+ooo-v+v'

πŸŽ€ gen_mode_change

takes two arguments, strings representing a set of IRC user modes before and after a change. returns a string showing what actually changed.

my $mode_change = gen_mode_change('abcde', 'befmZ');
$mode_change is now '-acd+fmZ'

πŸŽ€ parse_user

takes one param, a string representing a user in the form nick!user@hostname. in scalar context it gives you just the nickname. in list context it gives you a list of the nick, user and hostname, in that order. 🫢

πŸŽ€ is_valid_chan_name

takes one argument, a channel name to validate. returns true or false depending on whether that channel name is valid. you can also pass a second argument β€” an array of characters that count as allowed channel prefixes. defaults to ['#', '&']. πŸŽ€

πŸŽ€ is_valid_nick_name

takes one argument, a nickname to validate. returns true or false depending on whether the nickname is valid. ⭐

πŸŽ€ numeric_to_name

takes an IRC server numerical reply code (like '001') as an argument, and returns the matching name (like 'RPL_WELCOME'). 🧚

πŸŽ€ name_to_numeric

the reverse babe: takes an IRC server reply name (like 'RPL_WELCOME') and returns the matching numerical code (like '001'). 🌈

πŸŽ€ has_color

takes one param, a string of IRC text. returns true if it's got any IRC color codes in it, false if not. super handy if you want your bot to kick people for (ab)using colors. :) πŸ’€

πŸŽ€ has_formatting

takes one param, a string of IRC text. returns true if it contains any IRC formatting codes, false otherwise. ✨

πŸŽ€ strip_color

takes one param, a string of IRC text. returns that string stripped of all IRC color codes. bye bye rainbow. πŸŒˆπŸ’¨

πŸŽ€ strip_formatting

takes one param, a string of IRC text. returns the string stripped of all IRC formatting codes. 🌸

πŸŽ€ decode_irc

this one takes a byte string (aka an unmodified IRC message) and returns a text string. since the source encoding might have been UTF-8, you should store it with UTF-8 or some other Unicode encoding in your file/database/whatever, just to be safe. for the full tea, check "ENCODING". 🫢

use IRC::Utils qw(decode_irc);

sub message_handler {
    my ($nick, $channel, $message) = @_;

    # not wise, $message is a byte string of unknown encoding
    print $message, "\n";

    $message = decode_irc($what);

    # good, $message is a text string
    print $message, "\n";
}

🌟 CONSTANTS 🌟

use these bad boys to drop formatting and mIRC color codes into IRC messages. πŸ’…

normal text:

NORMAL

formatting:

BOLD
UNDERLINE
REVERSE
ITALIC
FIXED

colors:

WHITE
BLACK
BLUE
GREEN
RED
BROWN
PURPLE
ORANGE
YELLOW
LIGHT_GREEN
TEAL
LIGHT_CYAN
LIGHT_BLUE
PINK
GREY
LIGHT_GREY

each individual non-color formatting code can be cancelled with its corresponding constant, but you can also nuke all of them at once with "NORMAL". and listen β€” to cancel the effect of color codes you have to use "NORMAL", which of course also cancels all your other formatting codes as a side effect. no cap. 😭

$msg = 'This word is '.YELLOW.'yellow'.NORMAL.' while this word is'.BOLD.'bold'.BOLD;
$msg = UNDERLINE.BOLD.'This sentence is both underlined and bold.'.NORMAL;

✨ ENCODING ✨

πŸ’– Messages πŸ’–

okay so bestie, the only encoding requirement the IRC protocol puts on its messages is that they be 8-bits and ASCII-compatible. that's literally it. and because of that, most of the Western world lowkey settled on ASCII-compatible Latin-1 β€” usually Microsoft's CP1252, a Latin-1 variant β€” as a convention. recently, popular IRC clients (mIRC, xchat, certain irssi configurations) have begun sending a mixture of CP1252 and UTF-8 over the wire to allow more characters without breaking backward compatibility (too much, bestie 😭). They send CP1252 encoded messages if the characters fit within that encoding, otherwise falling back to UTF-8, and likewise autodetecting the encoding (UTF-8 or CP1252) of incoming messages. Since writing text with mixed encoding to a file, terminal, or database is not a good idea, you need a way to decode messages from IRC. decode_irc will do that. periodt. πŸ’…

πŸ¦‹ Channel names πŸ¦‹

The matter is complicated further by the fact that some servers allow non-ASCII characters in channel names. IRC modules generally don't explicitly encode or decode any IRC traffic, but they do have to concatenate parts of a message (e.g. a channel name and a message) before sending it over the wire. So when you do something like privmsg($channel, 'æði'), where $channel is the unmodified channel name (a byte string) you got from an earlier IRC message, the channel name will get double-encoded when concatenated with your message (a non-ASCII text string) if the channel name contains non-ASCII bytes.

To prevent this, you can't simply decode the channel name and then use it. '#æði' in CP1252 is not the same channel as '#æði' in UTF-8, since they are encoded as different sequences of bytes, and the IRC server only cares about the byte representation. Therefore, when using a channel name you got from the server (e.g. when replying to message), you should use the original byte string (before it has been decoded with decode_irc), and encode any other parameters (with encode_utf8) so that your message will be concatenated correctly. At some point, you'll probably want to print the channel name, write it to a log file or use it in a filename, so you'll eventually have to decode it, at which point the UTF-8 '#æði' and CP1252 '#æði' will have to be considered equivalent. no cap. ✨

        use Encode qw(encode_utf8 encode);

        sub message_handler {
            # these three are all byte strings
            my ($nick, $channel, $message) = @_;

            # bad: if $channel has any non-ASCII bytes, they will get double-encoded
            privmsg($channel, 'æði');

            # bad: if $message has any non-ASCII bytes, they will get double-encoded
            privmsg('#æði', $message);

            # good: both are byte strings already, so they will concatenate correctly
            privmsg($channel, $message);

            # good: both are text strings (Latin1 as per Perl's default), so
            # they'll be concatenated correctly
            privmsg('#æði', 'æði');

            # good: similar to the last one, except now they're using UTF-8, which
            # means that the channel is actually not the same as above
            use utf8;
            privmsg('#æði', 'æði');

            # good: $channel and $msg_bytes are both byte strings
            my $msg_bytes = encode_utf8('æði');
            privmsg($channel, $msg_bytes);

            # good: $chan_bytes and $message are both byte strings
            # here we're sending a message to the utf8-encoded #æði
            my $utf8_bytes = encode_utf8('#æði');
            privmsg($utf8_bytes, $message);

            # good: $chan_bytes and $message are both byte strings
            # here we're sending a message to the cp1252-encoded #æði
            my $cp1252_bytes = encode('cp1252', '#æði');
            privmsg($cp1252_bytes, $message);

            # bad: $channel is in an undetermined encoding
            log_message("Got message from $channel");

            # good: using the decoded version of $channel
            log_message("Got message from ".decode_irc($channel));
        }

See also Encode, perluniintro, perlunitut, perlunicode, and perlunifaq. iykyk. 🫢


πŸ‘‘ AUTHOR πŸ‘‘

Hinrik Γ–rn SigurΓ°sson <hinrik.sig@gmail.com> ("Hinrik" irc.perl.org, or "literal" @ FreeNode).

Chris "BinGOs" Williams <chris@bingosnet.co.uk>.


🌈 SEE ALSO 🌈

  • POE::Component::IRC
  • POE::Component::Server::IRC

πŸ’– that's the whole page, bestie! this was IRC::Utils(3pm) from perl v5.32.0, dated 2021-01-04. slay and hydrate. πŸ’…βœ¨

πŸ“¦ where this page comes from