📜 ⬆️ ⬇️

Writing an icq-bot on perl

If you do not know, but want to find out how to make a program that constantly hangs in memory and does something necessary from time to time, while you are writing in the Perl language - I will try to tell about it as clearly as possible.
I myself love detailed instructions that imply that the person reading it does not have to know everything that was missed. Let it be better that he himself will miss what he already knows. Especially because of something he may have a wrong idea.

Well, so as not to be boring, let's combine this example with another topic - icq, so that we would get icq-bot.


')

For starters - about demons.



Despite the development of perl under win32 and even the possibility of emulating the fork function, I will speak about unix. Anyway, the module needed to communicate with the icq server, under win32, is not sharpened.

Simply put, in Unix, each executable program spawns a process with a specific number. A program can run other programs — in this case, it becomes the parent program, and those that are launched can run the child program. If the parent program stops working, the children also stop working.

At the same time, icq-bot should work constantly, regardless of where it was launched from, and whether another program that started this process has stopped working. For example, I logged in to the server via ssh, from using bash I started the bot. After I close bash and ssh connection, the bot should continue working, as if nothing had happened.

This goal is served by the fork function, which starts a child process that is not associated with the terminal from which the parent was started. Following this, you must call the setsid function, which completes the work on isolating our process.

After these actions, the newborn demon, if he wants to be a good demon, needs to change the working directory to the root directory, in case it starts from the partition that was connected via mount. Otherwise, the partition will be busy and it will not be possible to disable it.

In addition, since the input from the terminal and the output to the terminal by the daemon will not be made - you must redirect these standard channels to zero. If you want to track the work of the bot, you can redirect the output to a log file.

The most tricky step is using the fork function. It creates another exactly the same process. The only difference between them is that the first one was a parent, and it must be completed, and the second is a child, and it should remain. Therefore, the process requires self-identification - who am I?
This is achieved by checking the result returned by the fork function. If it returns a non-zero value, this is the pid of the new child process that was started. So I'm a parent, and I'm leaving. If the pid is zero, it means that I am a daughter, and I work further.

As a result, the function of demonizing the process is as follows:

sub Daemonize { <br/>
return if ( $^O eq 'MSWin32' ) ; # , <br/>
chdir '/' or die "Can't chdir to /: $!" ; # unmount <br/>
umask 0 ; # <br/>
open STDIN , '/dev/null' or die "Can't read /dev/null: $!" ; # <br/>
open STDOUT , '>/dev/null' or die "Can't write to /dev/null: $!" ; # <br/>
open STDERR , '>/dev/null' or die "Can't write to /dev/null: $!" ; # <br/>
defined ( my $pid = fork ) or die "Can't fork: $!" ; # fork <br/>
exit if $pid ; # , id <br/>
setsid or die "Can't start a new session: $!" ; # <br/>
} <br/>


Message handling



The ICQ bot should send some messages and process the received ones. Let's make a simple bot option that works as a notifier - sends messages to the system where it is necessary, and simply issues a standard response to incoming messages. And for clarity, we will process a single command - quit.
And then it all depends on your imagination - you can handle incoming messages as you please, and here you can think up a lot of interesting things - from issuing system characteristics by command to artificial intelligence.

The easiest way to receive messages for distribution from files. You need to create a separate directory for outgoing mail files. Programs that need to send messages will add them to this folder.
The bot will only check it periodically and when new messages are found, send them.

If we do this, files like nnn.rrr will be created in the folder, where nnn is the recipient's icq UIN, and rrr is a random number so that files are not overwritten if several messages fall into one at a time.

Total - we need to scan the inbox, and for each file send its contents to the number specified in the file name.

sub CheckTasks { <br/>
# <br/>
my ( $file , $path , $text , $size , $recipient ) ; <br/>
<br/>
$path = "~username/icq/icq_tasks" ; # <br/>
<br/>
opendir DIR , $path ; <br/>
for $file ( grep /^\d+\.\d+$/ , readdir DIR ) { # nnn.rrr <br/>
$file =~/^ ( \d + ) \ . \d + $/ ; <br/>
$recipient = $1 ; # - UIN <br/>
$size = ( stat ( "$path/$file" ) ) [ 7 ] ; <br/>
if ( $size > 0 && $size < 200 ) { # <br/>
$text = ReadFile ( "$path/$file" ) ; # <br/>
unlink ( "$path/$file" ) ; # <br/>
$oscar -> send_im ( $recipient , $text ) ; # . , oscar - <br/>
} <br/>
} <br/>
} <br/>


Main program



We have almost done the auxiliary functions. Now we will make the main program.

To interact with the ICQ server, I use the Net :: OSCAR module. This is an object module, so we need to create an object that connects to the server, communicates with it, and periodically kicks the server so that it does not disconnect us. In addition, it is necessary to check the status of the connection, and in case of a break, reconnect. And of course, send and receive messages.

Go:

#!/usr/bin/perl -w <br/>
<br/>
use Net :: OSCAR ; # <br/>
use strict ; # strict <br/>
use POSIX qw ( setsid ) ; # POSIX, <br/>
<br/>
Daemonize ( ) ; # <br/>
<br/>
my ( $UIN , $PASSWORD , $oscar , $t ) ; <br/>
<br/>
$UIN = '123456' ; # UIN <br/>
$PASSWORD = 'mypass' ; # UIN <br/>
<br/>
$oscar = Net :: OSCAR -> new ( ) ; # icq <br/>
$oscar -> set_callback_im_in ( \&send_answer ) ; <br/>
# - <br/>
$t = 0 ; # <br/>
<br/>
while ( 1 ) { # <br/>
if ( ! $oscar -> is_on && ( time ( ) - $t ) > 120 ) { # 2 ! <br/>
$oscar -> signon ( $UIN , $PASSWORD ) ; # <br/>
$t = time ( ) ; # <br/>
} <br/>
$oscar -> do_one_loop ( ) ; # <br/>
CheckTasks ( ) if ( $oscar -> is_on ) ; # - <br/>
sleep ( 5 ) ; # 5 , <br/>
}


Let me explain in more detail some points:

The module is engaged in event handling, one of which is the receipt of an incoming message. In order for the bot to send responses to such messages, we hang up the send_answer () function call to this event:

$ oscar -> set_callback_im_in (\ & send_answer)

It might look like this:

sub send_answer ( ) { <br/>
my ( $oscar , $sender , $msg ) = @_ ; <br/>
# - <br/>
# , <br/>
if ( $msg eq "quit" ) { # . - <br/>
$oscar -> signoff ( ) ; # <br/>
exit ( ) ; # - <br/>
} <br/>
$oscar -> send_im ( $sender , ' , , .' ) ; <br/>
# <br/>
}
sub send_answer ( ) { <br/>
my ( $oscar , $sender , $msg ) = @_ ; <br/>
# - <br/>
# , <br/>
if ( $msg eq "quit" ) { # . - <br/>
$oscar -> signoff ( ) ; # <br/>
exit ( ) ; # - <br/>
} <br/>
$oscar -> send_im ( $sender , ' , , .' ) ; <br/>
# <br/>
}


So, we create an inbound handler and deal with outbound processing in an infinite loop. To check whether we are online, use the $ oscar-> is_on flag
And in order not to hammer too often with requests for a connection, we use a timer — the time of the last connection attempt is stored in $ t. In particular, this section will work after the program starts, to establish the first connection to the server.

If we are online, periodically we call the $ oscar-> do_one_loop () method, which keeps our bot logged in.

And if we are online, we check the inbox and, if necessary, send them.

We compose, save, run. The simplest icq-bot is ready! I use this bot to send notifications to me from the server.

What now?



Among the drawbacks of this bot, I note that the Net :: OSCAR module does not know how to send messages to offline users.

As homework I offer the following:
- on the fourth - to make so that when you first start the bot connects to icq immediately, and did not wait 2 minutes
- on the fourth with a plus - write the ReadFile function
- on Pyaterochka - to understand the documentation Net :: OSCAR, to teach the bot to determine whether the recipient of the message is offline and not to send him the message offline.

Source: https://habr.com/ru/post/111031/


All Articles