irc-descr/

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

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

!google [запрос]

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

#!/usr/bin/perl

# Googler IRC Bot
# (c) 2010 Alexandr A Alexeev
# http://remontka.com/

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 =~ /^:([^! ]+)![^ ]+ ([^rn]+)rn$/ ) {
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 символов. Вычитаем два символа-терминатора (rn) — получаем 510 символов. Также нужно учесть длину самой команды, пробелы между аргументами и тд. При посылке длинного сообщения, я бы советовал нарезать его на части по 256 символов. Или 140, если на сервере используется UTF-8.

Во-вторых, имена каналов начинаются с решетки. Решетка не может использоваться в никах, так что по первому аргументу команд типа PRIVMSG сразу видно, куда шлется сообщение, в «личку» или на канал.

В-третьих, в IRC можно выделять текст цветом и менять его фон. Найти бы и жестокого наказать того, кто придумал это расширение! Все ненужные украшательства в моем боте вырезаются функцией irc_msg_encode.

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

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

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

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

EnglishRussianUkrainian