IRC bot v PHP - 2. časť

Michal Pirchala  /  14. 12. 2010, 19:45

V druhej časti seriálu si predvedieme ukážkovú komunikáciu medzi klientom a serverom v protokole IRC.

Na posielanie a prijímanie dát som použil PHP skript, ktorý celú komunikáciu uložil do súbora. Prichádzajúce dáta sú označené „>", odchádzajúce zase „<".

< NICK mojbot
< USER mojbot mojbot irc.freenode.net :mojbot
> :asimov.freenode.net NOTICE * :*** Looking up your hostname...
> :asimov.freenode.net NOTICE * :*** Checking Ident
> :asimov.freenode.net NOTICE * :*** Found your hostname
> :asimov.freenode.net NOTICE * :*** No Ident response
> :asimov.freenode.net 001 mojbot :Welcome to the freenode Internet Relay Chat Network mojbot
> :asimov.freenode.net 002 mojbot :Your host is asimov.freenode.net[174.143.119.91/6667], running version ircd-seven-1.0.3
> :asimov.freenode.net 003 mojbot :This server was created Wed Jun 30 2010 at 20:01:02 CDT
> :asimov.freenode.net 004 mojbot asimov.freenode.net ircd-seven-1.0.3 DOQRSZaghilopswz CFILMPQbcefgijklmnopqrstvz bkloveqjfI
> :asimov.freenode.net 005 mojbot CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQcgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=freenode KNOCK STATUSMSG=@+ CALLERID=g :are supported by this server
> :asimov.freenode.net 005 mojbot SAFELIST ELIST=U CASEMAPPING=rfc1459 CHARSET=ascii NICKLEN=16 CHANNELLEN=50 TOPICLEN=390 ETRACE CPRIVMSG CNOTICE DEAF=D MONITOR=100 :are supported by this server
> :asimov.freenode.net 005 mojbot FNC TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: EXTBAN=$,arx WHOX CLIENTVER=3.0 :are supported by this server
> :asimov.freenode.net 251 mojbot :There are 605 users and 59856 invisible on 24 servers
> :asimov.freenode.net 252 mojbot 30 :IRC Operators online
> :asimov.freenode.net 253 mojbot 15 :unknown connection(s)
> :asimov.freenode.net 254 mojbot 39764 :channels formed
> :asimov.freenode.net 255 mojbot :I have 4002 clients and 1 servers
> :asimov.freenode.net 265 mojbot 4002 6991 :Current local users 4002, max 6991
> :asimov.freenode.net 266 mojbot 60461 66970 :Current global users 60461, max 66970
> :asimov.freenode.net 250 mojbot :Highest connection count: 6992 (6991 clients) (912419 connections received)
> :asimov.freenode.net 375 mojbot :- asimov.freenode.net Message of the Day -
> :asimov.freenode.net 372 mojbot :- Welcome to asimov.freenode.net in TX, USA! Thanks to
...
Uvítacia správa
...
> :asimov.freenode.net 372 mojbot :- Thank you for using freenode!
> :asimov.freenode.net 372 mojbot :-
> :asimov.freenode.net 376 mojbot :End of /MOTD command.
> :mojbot MODE mojbot :+i
> :frigg!~frigg@freenode/utility-bot/frigg PRIVMSG mojbot :VERSION
< NOTICE frigg :VERSION phpbot
< JOIN #skuska
> :mojbot!~mojbot@brest1.slnet.sk JOIN :#skuska
> :asimov.freenode.net 353 mojbot = #skuska :mojbot
> :asimov.freenode.net 366 mojbot #skuska :End of /NAMES list.
< PRIVMSG #skuska :text odoslaný do kanálu
> PING :asimov.freenode.net
< PONG :asimov.freenode.net
> PING :asimov.freenode.net
< PONG :asimov.freenode.net
< QUIT :Odchádzam
> :mojbot!~mojbot@brest1.slnet.sk QUIT :Quit: Odchádzam
> ERROR :Closing Link: brest1.slnet.sk (Quit: Odchádzam)

Vysvetlenie

Na začiatku sme serveru poslali príkaz nick, ktorým definujeme, pod akým menom budeme na IRC sieti vystupovať. Nasleduje príkaz user so základnými informáciami o nás (meno užívateľa, skutočné meno a pod.). Ak všetko prebehne v poriadku, server nám zašle základné informácie o sebe (koľko má pripojených užívateľov, vytvorených kanálov a pod.) a uvítaciu správu, ktorú som skrátil. Táto správa sa konči riadkom „> :asimov.freenode.net 376 mojbot :End of /MOTD command. " (MOTD = Message of the Day). Nasleduje k nám správa od bota siete, ktorý zisťuje akého klienta používame na pripojenie k IRC. Odpovieme mu príkazom NOTICE. Pripojíme sa ku kanáli #skuska a server nám pošle zoznam pripojených užívateľov. Pošleme tam správu „text odoslaný do kanálu " a ostávame pripojený. Po nejakom čase, keďže sme dlhšiu dobu neposlali serveru žiadnu správu, si server príkazom PING overí, či sme ešte pripojení. Toto sa opakuje vždy, keď sme neaktívni. Potom serveru príkazom QUIT oznámime, že sa chceme odpojiť so správou „Odchádzam". Server nám odpovie a ukončí s nami spojenie.

Komunikácia so serverom s naším programom
K nášmu jadru z minula doplníme pár vecí. Vytvoríme sekciu Funkcie do ktorej pridáme funkciu send(), ktorá do socketu zapíše dáta, ktoré jej predáme v parametri.

...
//Funkcie
function Send ($cmd){
    $cmd = $cmd."\n\r";
    global $server;
    fwrite($server['SOCKET'], $cmd, strlen($cmd));
    echo "< $cmd";
}
...

K parametru $cmd pridáme nový riadok, sprístupnime si globálnu premennú $server, do socketu zapíšeme obsah premennej $cmd a hneď ho aj vypíšeme na štandardný výstup (toto nám neskôr pomôže pri odhaľovaní chýb).

Ďalej po podmienke, ktorá overuje či pripojenie prebehlo v poriadku pridáme tento kód:

...
Send("NICK $nick");
Send("USER $nick $nick irc.freenode.net :$nick");
stream_set_timeout($server['SOCKET'], 5);

while(true){
    $msg = trim(fgets($server['SOCKET'], 1024));
    if (!empty($msg)) echo $msg."\n";

    if (empty($msg)){
    send("JOIN $channel");sleep(3);
    send("PRIVMSG $channel :text odoslaný do kanála");
    send("QUIT");
    die();
}
}
...

Najprv pošleme príkazy NICK a USER. Funkciu stream_set_timeout() bližšie vysvetlím neskôr. Pokračujeme na prvý pohľad nekonečným cyklom, ktorý bude ukončený iba funkciou die() (ukončením behu skriptu). Pri každom cykle sa zo socketu zoberú dáta poslané serverom. Ak server nepošle žiadne dáta, náš skript bude stále čakať a teda nebude vykonávať nič iné. Tu je opodstatnenie využitia funkcie stream_set_timeout(). Vysvetlím to na tomto príklade:

Skript ma za úlohu poslať čo najrýchlejšie po unixovom čase 900 správu užívateľovi admin. Funkcia, ktorá kontroluje aký je čas a odosiela správu sa spúšťa pri každom cykle. Práve je unixový čas 890, skript sa dostane na funkciu fgets, ktorá bude čakať dovtedy, kým od servera nedostane nejaké dáta. Dáta od servera môžeme dostať napríklad pri najbližšom pingu, čo ale môže byť aj 100 sekúnd. Po tom, čo by sme dostali dáta, funkcia fgets by už vrátila nejakú hodnotu a skript by pokračoval ďalej. Dostal by sa k funkcii, ktorá pošle správu užívateľovi admin ale s viac ako 100 sekundovým meškaním. To je dosť nepraktické, ale našťastie funkciou stream_set_timeout() sa dá nastaviť maximálny čas, ktorý bude funkcia fgets čakať na dáta od servera.

Do premennej $msg si uložíme dáta ktoré nám poslal server. Ak nieje prázdna, vypíšeme ju. Keď nám server prestane posielať uvítaciu správu (ubehne 5 sekúnd, ktoré funkcia fgets čakala na dáta zo socketu), premenná $msg bude prázdna a splní sa podmienka „if (empty($msg))", ktorá spustí príkazy ktorými sa pripojíme na kanál uložený v premennej $channel (ktorá je definovaná v sekcii nastavenia), počká 3 sekundy, pošle správu „text odoslaný do kanála", oznámi, že sa chce odpojiť a ukonči beh skriptu.

Upravené jadro

//Pripojenie k MySQL databaze
define('SQL_NAME', 'meno');
define('SQL_PASS', 'heslo');
define('SQL_HOST', 'server');
define('SQL_DBNM', 'databaza');
$dbc = mysql_connect(SQL_HOST, SQL_NAME, SQL_PASS);
if (!$dbc) die('Nedá sa pripojiť k databázovému serveru');
mysql_select_db(SQL_DBNM) or die ('Nedá sa vybrať databáza.');
mysql_query('SET NAMES UTF8');     //kodovanie v UTF-8

//Neobmedzeny casovy limit na vykonanie skriptu
set_time_limit(0);

//Nastavenie
$channel = '#naskanal';
$nick = 'mojbot';

//Funkcie
function Send ($cmd){
    $cmd = $cmd."\n\r";
    global $server;
    fwrite($server['SOCKET'], $cmd, strlen($cmd));
    echo "< $cmd";
}

//Pripojenie na server
$server = array();
$server['SOCKET'] = fsockopen("irc.freenode.net", 6667, $errno, $errstr);

if ($server['SOCKET']){

    Send("NICK $nick");
    Send("USER $nick $nick irc.freenode.net :$nick");
    stream_set_timeout($server['SOCKET'], 5);

    while(true){
        $msg = trim(fgets($server['SOCKET'], 1024));
        if (!empty($msg)) echo ">".$msg."\n";
    
        if (empty($msg)){
            send("JOIN $channel");sleep(3);
            send("PRIVMSG $channel :text odoslaný do kanála");
            send("QUIT");
            die();
        }
    }

} else echo "Nemôžem sa pripojiť na server";
?>

Záver

Ukázali sme si IRC komunikáciu a prvýkrát sa pripojili na IRC kanál. Nabudúce vyriešime odpovede na správu VERSION, PING-y a naprogramujeme podporu modulov.

 

Neprehliadnite: