Краткое описание протокола IRC и пример бота
25 ноября 2010
Вот решил написать небольшой пост о протоколе IRC. Во-первых, что-то я давно не пополнял свою коллекцию описаний сетевых протоколов, а во-вторых это в тему про ботнеты, о которых недавно шла речь.
Вообще-то IRC прекрасно описан в RFC, русском переводе этого RFC и Википедии. Потому я решил не ограничиваться одним его описанием и выложить пример простенького бота. Бот этот заходит на канал и ждет команду:
Получив ее, бот посылает запрос поисковику, кидает первую ссылку из выдачи в чат и ждет следующих команд.
# Googler IRC Bot
# (c) 2010 Alexandr A Alexeev
# http://eax.me/
use IO::Socket::INET;
use URI::Escape;
use Text::Iconv;
use strict;
my ($server, $port, $botnick, $charset) =
qw/irc.dalnet.ru 6667 googler cp1251/;
my $pass = "";
my @channels = qw/googlertest/;
my $opt = "--timeout=10 --no-check-certificate --user-agent='Mozilla/5.0 (X11; U; FreeBSD i386; ru-RU; rv:1.9.1.10) Gecko/20100625 Firefox/3.5.10'";
my $conv = Text::Iconv->new($charset, "utf-8");
while(1) {
print "CONNECTING: server = $server, port = $port...\n";
my $irc = IO::Socket::INET->new(
PeerAddr => $server,
PeerPort => $port,
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => 10
);
if(defined $irc) {
print $irc "USER $botnick localhost localhost $botnick\r\n";
print $irc "NICK $botnick\r\n";
print $irc "PASS $pass\r\n" if $pass;
print $irc "JOIN #$_\r\n" for @channels;
while(my $line = <$irc>) {
$line = $conv->convert($line);
if($line =~ /^PING \:([a-zA-Z\-\.0-9]+)/){
print $irc "PONG $1\r\n";
} elsif($line =~ /^\:([^\!\ ]+)\![^\ ]+\ ([^\r\n]+)\r\n$/) {
my $nick = $1; $line = $2;
if(my($channel,$msg) = $line=~/^PRIVMSG #([^\ ]+) \:(.+)$/) {
# см http://ru.wikipedia.org/wiki/IRC#CTCP
if(my ($action) = $msg =~ /^\x01ACTION ([^\x01]+)\x01?$/){
print irc_msg_encode("[$channel] *** $nick $action\n");
} else {
print irc_msg_encode("[$channel] $nick: $msg\n");
if($msg =~ /\!google (.*)/) {
my $result = do_google($1);
print $irc "PRIVMSG #$channel :$nick, $result\r\n";
}
}
} elsif(my($chan,$nick) = $line=~/^KICK #([^\ ]+) ([^\ ]+)/) {
if($nick eq $botnick) {
print "DEBUG: i'm kicked from #$chan\n";
last;
}
}
} # if($line =~ /^PONG ...
} # while($line = <$irc> ...
} else { # if(defined $irc) ...
print "CONNECTION FAILED!\n";
}
sleep 300;
}
# поиск в Google
sub do_google {
my $query = $_[0];
my $url = "http://google.com/search?q=".uri_escape($query);
my $data = `wget $opt -q '$url' -O -`;
return "query failed - error $?" if $?;
return $1 if $data =~ /<h3 class="r"><a href="([^"]+)"/i;
return "nothing found";
}
# вырезаем выделения цветом и тп
sub irc_msg_encode {
my $text = $_[0];
$text =~ s/\x0F//g;
$text =~ s/\x02//g; $text =~ s/\x1F//g;
$text =~ s/\x03([0-9]{1,2}(\,[0-9]{1,2})?)?//g;
$text;
}
Пример работы скрипта:
На базе этого примера можно написать бота, который постит новости, определяет погоду и тд. Считайте это домашним заданием.
Краткое описание основных сообщений сервера:
- PING :[что-то] — время от времени посылается сервером, на случай, если клиент повиснет. Получив такую команду, нужно как можно быстрее ответить PONG (см ниже), иначе сервер может разорвать соединение.
- :[ник]![юзер@хост] [событие] — [событие], созданное пользователем [ник]. Например, [ник] мог послать вам сообщение ([событие] = «PRIVMSG …») или выкинуть вас с канала ([событие] = «KICK …»).
- PRIVMSG [кому] :[сообщение] — новое [сообщение], личное ([кому] = ваш ник) или не личное ([кому] = #channel). Как определить отправителя, см в предыдущем пункте.
- KICK [канал] [ник] … — пользователя [ник] кикнули с канала [канал]. Кто кикнул, определяется так же, как и в случае с PRIVMSG.
Основные команды клиента:
- USER — представиться серверу, см код бота.
- PASS [пароль] — если для захода на сервер нужен пароль.
- NICK [ник] — сменить ник.
- PONG :[что-то] — ответ на команду PING.
- JOIN #[канал] — зайти на канал.
- PART #[канал] — уйти с канала. Второй необязательный аргумент — причина ухода.
- PRIVMSG [кому] :[сообщение] — послать сообщение конкретному пользователю или на канал.
- QUIT — отсоединиться от сервера.
Несколько замечаний. Во-первых, максимальная длина одной строки в протоколе составляет 512 символов. Вычитаем два символа-терминатора (\r\n) — получаем 510 символов. Также нужно учесть длину самой команды, пробелы между аргументами и тд. При посылке длинного сообщения, я бы советовал нарезать его на части по 256 символов. Или 140, если на сервере используется UTF-8.
Во-вторых, имена каналов начинаются с решетки. Решетка не может использоваться в никах, так что по первому аргументу команд типа PRIVMSG сразу видно, куда шлется сообщение, в «личку» или на канал.
В-третьих, в IRC можно выделять текст цветом и менять его фон. Найти бы и жестокого наказать того, кто придумал это расширение! Все ненужные украшательства в моем боте вырезаются функцией irc_msg_encode.
Наконец, если первым символом строки является двоеточие, то все последующие пробелы следует считать частью этой строки, а не разделителями аргументов команды. Например, мы можем уйти с канала, сказав свое последнее слово, с помощью команды:
Ну вот по большому счету и все. Как я уже писал когда-то, в 90% случаев используется лишь 10% протокола. Сравните одну страничку этого поста и 65 страниц RFC. Если вы собрались писать собственного IRC бота или даже небольшой IRC-клиент/сервер, то этой заметки наверняка окажется достаточно. Ну а если нет, дополнительную информацию всегда можно найти в Вики и RFC.
Дополнение: Простейший IRC-бот на Python, а также при чем тут Slack, Gitter и прочие веб-чаты
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.