← На главную

Краткое описание протокола IRC и пример бота

Вот решил написать небольшой пост о протоколе IRC. Во-первых, что-то я давно не пополнял свою коллекцию описаний сетевых протоколов, а во-вторых это в тему про ботнеты, о которых недавно шла речь.

Вообще-то IRC прекрасно описан в RFC, русском переводе этого RFC и Википедии. Потому я решил не ограничиваться одним его описанием и выложить пример простенького бота. Бот этот заходит на канал и ждет команду:

!google [запрос]

Получив ее, бот посылает запрос поисковику, кидает первую ссылку из выдачи в чат и ждет следующих команд.

#!/usr/bin/perl # Googler IRC Bot # (c) 2010 Alexandr 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; }

Пример работы скрипта:

Пример IRC бота

На базе этого примера можно написать бота, который постит новости, определяет погоду и тд. Считайте это домашним заданием.

Краткое описание основных сообщений сервера:

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

Наконец, если первым символом строки является двоеточие, то все последующие пробелы следует считать частью этой строки, а не разделителями аргументов команды. Например, мы можем уйти с канала, сказав свое последнее слово, с помощью команды:

PART #channel :всем пока, пора работу работать!

Ну вот по большому счету и все. Как я уже писал когда-то, в 90% случаев используется лишь 10% протокола. Сравните одну страничку этого поста и 65 страниц RFC. Если вы собрались писать собственного IRC бота или даже небольшой IRC-клиент/сервер, то этой заметки наверняка окажется достаточно. Ну а если нет, дополнительную информацию всегда можно найти в Вики и RFC.

Дополнение: Простейший IRC-бот на Python, а также при чем тут Slack, Gitter и прочие веб-чаты