PDA

Просмотр полной версии : Пишем простого Irc бота на Pel


ENFIX
16.06.2007, 23:30
Пишем IRC бота на PERL

Здравствуйте! Сегодня я вам расскажу, как написать простенького irc бота на PERL.
Сам недавно только взялся за изучение этого языка. Как то раз скачал книгу Клинтон Пирс - "Освой самостоятельно Perl за 24 часа"
Скажу сразу - книга так себе, только основы (Но мне как раз это и нужно было)
И вот, когда я дошел до модулей, наткнулся на IO::Socket. Тем вечером я еще сидел в ирке, и подумал: "А почему бы не попробовать..."

Ну вот, лирическое отступление кончилось =) Начнем!

И так, что нам понадобится:
ActivePerl, подключение к интернету, mIRC(или другой клиент для IRC), минимальные знания PERL, а также документация по протоколу IRC со стороны клиента (я брал http://tools.ietf.org/html/rfc2812)

Собственно вот... Открываем текстовый редакор, пишем строчки:

#!/usr/bin/perl

Думаю, тут комментировать ненадо, но все таки. Эта строка указывает путь к интерпритатору PERL.

Подключаем модули:

use strict;
use IO::Socket;

Подключили =)

my $server = "irc.inattack.ru";
my $port=6667;
my $chanel = "#inattack";
my $nick = "MIKE";
my $ident = "mike";
my $name="Майк";

В переменной $server хранится адрес irc сервера (вписываем нужный)
В переменной $port хранится порт irc сервера (вписываем нужный)
И в переменной $chanel хранится адрес канала
В переменной $nick хранится Ник бота
В переменной $ident хранится Идент бота
В переменной $name хранится имя бота (увидите при хуизе)

Идем дальше:

my $socket = new IO::Socket::INET(PeerAddr => $server, PeerPort => $port, Proto => "tcp");

в этой строке создаем новый объект IO::Socket::INET и ссылка на него присваевается переменной $socket

Ну вот, переменные мы задали, начало есть =)
Теперь идем к документации, видим, что первым делом мы должны послать

Command: NICK
Parameters: <nickname>

Так и сделаем 8)

print $socket "NICK $nick\r\n";

В переменную $socket вы вводим NICK $nick, \r\n - перевод каретки на следущую строку (то же что и Enter)

Идем дальше, следущей командой должна быть:

Command: USER
Parameters: <user> <mode> <unused> <realname>

Собственно что мы и сделаем:

print $socket "USER $ident 8 * :$name\r\n";

Что далее? Далее мы заходим на нужный нам канал, что говорит документация?

Command: JOIN
Parameters: ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] )
/ "0"

Реализовываем:

print $socket "JOIN $chanel\r\n";


Готово, бот законнектился на ирк сервер, зашел на канал, НО, спустя какое то время его выкинет по причине (Ping timeout)
Этого нам конечно же не нужно. По протоколу, спустя какое то время, сервер на посылает запрос вида "PING : irc.site.com", а нам надо отправить ответ PONG.
Идем к документации, видим:

Command: PONG
Parameters: <server> [ <server2> ]

"Шкодим" через цикл

while (my $body = <$socket>) {
chop $body;
if ($body =~ /^PING(.*)$/i) {
print $socket "PONG $1\r\n";
}
}

Готово! Теперь наш бот умеет коннектиться к серверу, заходить на канал и "не вылетать" 8)
Но все равно чего-то не хватает, правда? =)
Давайте сделаем, чтоб при заходе на канал, он говорил привет:

print $socket ("PRIVMSG $chanel : Привет всем =)\r\n");

Отлично! Но бот без команд, это не бот, так? =)

Приведу пример некоторых: (пишем в цикл while() )

if ($body =~ /^.*!yandex(.*)$/i) {
print $socket ("PRIVMSG $chanel Сам ищи, я не лох :-P\r\n");
}
if ($body =~ /^.*!bye(.*)$/i) {
print $socket ("QUIT\r\n");
}

Собственно вот он, самый простой бот готов! =)

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

#!/usr/bin/perl
use strict;
use IO::Socket;
my $server = "irc.inattack.ru";
my $port=6667;
my $chanel = "#inattack";
my $nick = "MIKE";
my $ident = "mike";
my $name="Майк";
my $socket = new IO::Socket::INET(PeerAddr => $server, PeerPort => $port, Proto => "tcp") or die "lol";
print $socket "NICK $nick\r\n";
print $socket "USER $ident 8 * :$name\r\n";
print $socket "JOIN $chanel\r\n";
print $socket ("PRIVMSG $chanel : Рад вновь увидеть вас всех!!\r\n");
while (my $body = <$socket>) {
chop $body;
if ($body =~ /^PING(.*)$/i) {
print $socket "PONG $1\r\n";
}
if ($body =~ /^.*!yandex(.*)$/i) {
print $socket ("PRIVMSG $chanel Сам ищи, я не лох :-P\r\n");
}
if ($body =~ /^.*!bye(.*)$/i) {
print $socket ("QUIT\r\n");
}
}

Спасибо за внимание!

Piflit
17.06.2007, 00:15
1. может значение $port тоже надо писать в кавычках?
2. команды серверу должны начинаться со слеша "/"

KSURi
17.06.2007, 01:21
про perldoc рассказывать даже не пытаюсь=\

/^.*!yandex(.*)$/i
регекс ваще не в тему... надеюсь сам разберешься почему

кароче незачот

Isis
17.06.2007, 01:50
Зачем из этого делать статью?Выложил бы своего бота, кому надо переделают...
А вообще для начала дай хотябы одну книгу по языку pel :D :D

Thanat0z
17.06.2007, 02:54
Зачем из этого делать статью?

Перенес в Кодинг

ENFIX
17.06.2007, 04:42
>1. может значение $port тоже надо писать в кавычках?
Числа можно писать без кавычек
>2. команды серверу должны начинаться со слеша "/"
Нет, это в мирке и др так сделано. Прочитай документацию.
>регекс ваще не в тему... надеюсь сам разберешься почему
Да.. ступанул =) Учу... соррь
>А вообще для начала дай хотябы одну книгу по языку pel
Очепятался, бывает ;)

За критику спасибо!

Cobalt
18.06.2007, 09:46
Раз пошел такой колинкор предлагаю заценить и это кто не видел: http://www.gfs-team.ru/?act=articles&pact=55 - Perl.Irc Bot


В этой статье мы попытаемся охотиться сразу на двух зайцев. А именно, разберем
IRC протокол и напишем скрипт на Perl, который будет выполнять функции IRC бота.
Эта статья посвящена в первую очередь програмистам которые хотят изучить Perl,
и подразумевается что они хотябы знают синтаксис данного языка.

Для начала опишем настроечные переменные, а именно:

1. Адрес и порт сервера.
2. Ник и идент
3. IRC канал на котором будет крутиться наш бот.

Это делается так:

$host="irc.lxxl.info";
$port="6667";
$nick="MyBot";
$ident="MyBot";
$chan="#GFS";

Советую использовать в качестве сервера именно irc.lxxl.info, он не очень строг
к вашему иденту ). Далее создадим сокет соединения с сервером:

use Socket;
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname(\'tcp\'))
or die "Couldn`t create socket : $!\n";
$iaddr=inet_aton($host);
$paddr=sockaddr_in($port,$iaddr);
print "\n>> Connecting to $host:$port...\n";
connect(SOCK, $paddr)
or die "Couldn`t connect to $host:$port : $!\n";
for($i=0; $i<4; $i++){
$data=;
print($data);
}

Рассмотрим этот кусок кода более подробно. use Socket - сдесь мы указали что
необходимо использовать стандартную библиотеку Socket, которая содержит
функции и переменные для работы с сокетами. Функция socket предназначена
для создания сокета. В ее первом параметре указывается идентификатор по которому
мы в последствии будем обращаться к сокету. Во втором указывается комуникацион-
ный домен, в нашем случае это PF_INET, что означает домен Интернет. Так же
существует домен unix - PF_UNIX. Третий параметр указывает тип сокета. Мы будем
использовать SOCK_STREAM - этот тип обеспечивает последовательный, надежный
поток байтов. Так же существуют Datagram socket и Raw socket, но о них как-
нибудь в другой раз. И наконец в последнем параметре определяется протокол.
Делается это функцией getprotobyname(\'tcp\'), которая указывает в качестве
протокола TCP. Так же возможно использовать и udp, ip и т.д. Функция
getprotobyname возвращает название протокола в более удобном для функции socket
виде.

Далее необходимо преобразовать адрес сервера в бинарную последовательность.
Для этого используется функция inet_aton(). И дописать в нее порт к которому
мы будем соединяться функцией sockaddr_in(). Теперь соединим сокет с сервером
функцией connect(). Если все прошло успешно, в указателе SOCK у нас будет
поток сокета. Работать с ним можно точно также как и с потоком файла. Например
при успешном соединении сервер должен послать нам четыре строки служебной инфы.
Вот мы их прочитаем и выведем на экран в цикле For.

Теперь необходимо послать серверу информацию о себе. А именно идент и ник.
Для этого нам понадобится вункция send() которая принимает в качестве аргумента
три параметра: идентификатор сокета, строку которую необходимо отослать и 0.
Вместо нуля могут быть установлены следующие флаги:

MSG_OOB - Посылать/получать данные, характерные для сокетов типа
SOCK_STREAM
MSG_DONTROUTE - Посылать данные без маршрутизации пакетов. Как правило
используется диагностическими программами и процессами
управляющими таблицами маршрутизации.

Посылаем данные, читаем 10 строк и выводим на экран:

print ">> Sending NICK and IDENT...\n";
send (SOCK, "NICK $nick\n", 0);
send (SOCK, "USER $ident localhost localhost :$nick\n", 0);
for($i=0; $i<10; $i++){
$data=;
print($data);
}

В соответствии со стандартом IRC протокола, мы обязаны при соединении
уведомить сервер о том кто мы такие. Это делают IRC команды NICK и USER. Их
синтаксис не сложно понять из запросов которые мы только что отослали серверу.
Команда JOIN предназначается для входа на какой-либо канал. Отсылаем

print ">> Join chanel $chan...\n";
send (SOCK, "JOIN $chan\n", 0);

для входа на указаный канал. Заметь, что каждая IRC команда оканчивается
обязательным символом \n (перенос строки). Ведь ты нажимешь Enter в mIRC ?=)
Давай запустим бесконечный цикл приема сообщений:

while($data=){
print($data);
}
close(SOCK);

Теперь твой бот висит на канале и слушает что ему говорят. В окошке консоли
отлично видно какая информация приходит с сервера. Но возникает одна существен-
ная проблема: нашего бота через некоторое время выкидывает с сервера по таймауту
Это происходит из-за того что бот не отвичает на пинги посылаемые сервером.
Да да, на сервере установлен миханизм защиты от зомби. Каждому своему клиенту
через определенное время посылается команда PING, на которую в течении 256-ти
секунд клиент обязан ответить командой PONG с копией переданых данных. Если
же сервер по прошествии 256-ти секунд не получает ответа, он закрывает свое
соединение с клиентом, считая его зависшим.

Выходом из данной ситуации будет служить если мы пропишем в цикле бота обработ-
чик пингов.Т.е. нам необходимо уловить когда придет комада содержащая слово PING
взять данные пришедшие вместе с ней, и отослать в месте с командой PONG обратно
серверу.

@part=split(/:/,$data);
if(@part[0] eq "PING "){
print ">> PONG :@part[1]\n";
send (SOCK, "PONG :@part[1]\n", 0);
}

Функция split разбирает строку в переменную, считая в качестве разделителя
первый аргумент. В нашем случае это простейшее регулярное выражение /:/, которое
указывает в качестве разделителя двоеточие. После у нас в массиве @part
содержатся элементы строки $data. eq - аналогично знаку равенства для
строк.

Теперь было бы не плохо если бы наш бот хоть что-то бы делал, а не просто сидел
и молчал на канале. Давай напишем обработчик излюбленной всеми команды !пиво ).
Получая такую команду наш бот должен проверить, идет ли за ней чей-то ник. Если
да, то он должен сказать что пославший команду налил указаному нику пива. Если
же не идет, то бот обязан сказать что пославший команду налил всем пива.

### парсим полученую строку.
chop($data); ## обрезаем символы конца у строки
chop($data); ## обрезаем символы конца у строки
@part=split(/:/,$data);
@tmp=split(/!/,@part[1]);
$_user=@tmp[0]; # $_user - задавший команду
@tmp=split(/ /,@tmp[1]);
$_id=@tmp[0]; # $_id - идент юзера
@tmp=split(/ /,@part[1]);
$_cmd=@tmp[1]; # $_cmd - IRC команда
@tmp=split(/ :/,$data);
$_data=@tmp[1]; # $_data - команда

Эти переменные пригодятся тебе а дальнейшем. Теперь осталось только проверить
$_data на наличие !пиво в начале строки. делается это через регулярные
выражения:

if($_data=~/^!пиво/){
## $_data содержит !пиво в начале строки
}

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

@part=split(/ /,$_data);
if(@part[1]){
send (SOCK, "PRIVMSG $chan :$_user налил пива @part[1]\n", 0);
}else{
send (SOCK, "PRIVMSG $chan :$_user налил всем по пиву\n", 0);
}

IRC команда PRIVMSG предназначена для отправки сообщений. Если вместо $chan
указать ник юзера, то сообщение будет отправлено ему в приват.

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

Ну вот и все на cегодня. До скорых встреч! =)


© Cobalt


На сайте есть исходник. Плюс немного улучшенная версия перлового бота.

KSURi
18.06.2007, 12:58
В обоих статьях не увидел нормального парсинга...

/^:(.+?)!(.+?)@(.+?)\sPRIVMSG\s(#.+?)\s:(.+?)$/
# $1 - nick
# $2 - ident
# $3 - host
# $4 - chan
# $5 - msg text


PS: perldoc Net::IRC

ENFIX
18.06.2007, 15:24
Cobalt, прочел, только он криво работает

PS: perldoc Net::IRC
В статье я также хотел немного познакомить с протоколом IRC ;)

genom--
19.06.2007, 20:52
правильно что убрали из статей --- а так старо как мир -- но чтобы не совсем наезжали на новечка похвалю чуток =) молодца для начала нормально -- я как вспомню что сам сначала писал -- это пиздец был

UnrealMAN
19.06.2007, 21:01
print $socket...
Если уж использовал IO::Socket, то код можно было бы сделать более приятным, пользуясь методами send и recv, т.е.
$socket->send();
Хотя не смертельно :)