Ukládání historie zpráv IM klientů do databáze

Taky míváte při učení takové stavy, že vás napadá milion věcí, které byste mohli zrovna teď dělat a trávit tak čas rozhodně efektivněji než tím blbým učením třistastránkových skript, ze kterých nepoužijete v životě ani jednu definici ? Pokud ano, tak vítejte v klubu :-) Zrovna jsem projížděl skripta a snažil se vyznat v tom nepřehledném toku textu, když mě napadla dvoustátřicátášestá věc, co bych mohl udělat a dokonce jsem to realizoval.

Štvalo mě, že používám více klientů pro instant messaging napříč operačními systémy a je velmi složité zpětně hledat v historii zpráv a díky několika zálohám skoro nemožné, protože v nich mám značný nepořádek. Takže mě nepadlo udělal parser na logy z těchto programů, který by zprávy ukládal do databáze a já bych měl vše přehledně na jednom místě.

Miranda

Aktuálně nejvíce používám Mirandu, takže jsem začal u ní. Data z profilu jsou bohužel uložena v jednom souboru, proto je potřeba použít plugin Message Export, který průběžně ukládá všechny rozhovory do textových souborů na disku a ty pak můžu snadno analyzovat. Je tu ale jeden háček – tento plugin je starý a proto nefunguje v nové Mirandě (verze 0.8). Naštěstí existuje řešení v podobě knihovny Bridge, která pro starý plugin vytvoří rozhranní kompatibilní s novou Mirandou.

Pozn.: Do textových souborů lze exportovat kompletní dosavadní historii stisknutím jednoho tlačítka ! Lze to udělat v možnostech Mirandy, tam vybereme Doplňky – Export zpráv a po nastavení složky stiskneme tlačítko Exportovat vše.

Výsledný textový soubor (plain text) vypadá takto:

------------------------------------------------
      History for
User      : _en2cs
Protocol  : JABBER
UIN       : 0
FirstName :
LastName  :
Age       : 0
Gender    :
e-mail    :
Nick      : _en2cs
City      :
State     :
Phone     :
Homepage  :
- About -

------------------------------------------------
KyberMonty  24.5.2009 12:57:36 Unrecoverable

_en2cs      24.5.2009 12:57:36 Nevymahatelných

KyberMonty  26.5.2009 22:21:00 whipped

_en2cs      26.5.2009 22:21:01 šlehaný

Opět jsem narazil na další háček – plugin má asi nějakou chybu a podle nálady střídá kódování uloženého textu – někdy je to UTF-8 a někdy CP1250 (většinou je ve znakové sadě Windows uložen mnou odeslaný text). Ale i to jsem nakonec vyřešil. Takto vypadá výsledný skript v PHP:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <title>Miranda history parser</title>
  </head>
  <body>

<?php

set_time_limit( 1800 );

define( 'DIR', 'miranda/' );

require_once 'config.php';
require_once 'dibi/dibi.php';

dibi::connect( $connect_params );


$names = array( );

function getNickID( $nick )
{
    global $names;

    foreach ( $names as $name )
    {
        if ( strcmp( $nick, $name[ 'nick' ] ) == 0 ) return $name[ 'id' ];
    }

    $res = dibi::query( 'SELECT [id] FROM [history_nick] WHERE [nick] = %s', $nick );

    if ( $res->rowCount( ) == 0 )
    {
        $arr = array(
            'nick%s' => $nick
        );
        dibi::query( 'INSERT INTO [history_nick]', $arr );

        $id = dibi::insertId( );
    }
    else
    {
        $id = $res->fetchSingle( );
    }

    $tmp = array( 'nick' => $nick, 'id' => $id );
    array_push( $names, $tmp );

    return $id;
}


$d = dir( DIR );

// postupně zpracovávám všechny soubory z daného adresáře
while ( false !== ( $entry = $d->read( ) ) )
{
    if ( $entry[ 0 ] == '.' ) continue;

    $file = fopen( DIR . $entry, 'r' );

    $state = 0;
    $msg = '';

    while( !feof( $file ) )
    {
        $line = fgets( $file );

        // pokud není aktuální řádek v kódování UTF-8, pak ho na toto kódování převedu
        if ( !mb_check_encoding( $line, 'UTF-8' ) ) $line = iconv( 'cp1250', 'utf-8', $line );

        $first_char = ord( $line[ 0 ] );

        // na začátku si zjistím s kým jsem si psal, abych mohl všechny zprávy
        // z tohoto souboru označit jednoznačným identifikátorem
        if ( $state == 1 && strncmp( $line, 'User', 4 ) == 0 )
        {
            preg_match( '~^User\s+:\s+(.+)$~', $line, $matches );

            $chat_with = getNickID( trim( $matches[ 1 ] ) );
        }
        // pod sebou může být několik řádků se zprávami od jednoho člověka,
        // proto si je seskupuju do jedné proměnné
        elseif ( $state > 1 && $first_char != 13 )
        {
            $msg .= trim( $line ) . "\n";
        }

        if ( $state > 1 && $first_char == 13 )
        {
            preg_match( '~^(.+)\s{2,}([0-9]+\\.[0-9]+\\.[0-9]+) ([0-9]+:[0-9]+:[0-9]+)~', $msg, $matches );

            $msg = substr( $msg, strlen( $matches[ 0 ] ) + 1 );
            $matches[ 1 ] = trim( $matches[ 1 ] );

            $nick = getNickID( $matches[ 1 ] );

            $arr = array(
                'nick_id%i' => $nick,
                'time%t' => $matches[ 2 ] . ' ' . $matches[ 3 ],
                'message%s' => trim( $msg ),
                'chat_with%i' => $chat_with
            );
            dibi::query( 'INSERT INTO [history_msg]', $arr );

            echo '<p style="border: 1px solid blue">***' . $matches[ 1 ] . '***'
                . $matches[ 2 ] . '***' . $matches[ 3 ] . '***' . nl2br( $msg ) . "</p>\n";

            $msg = '';
        }

        if ( strncmp( $line, '------------------------------------------------', 48 ) == 0 )
        {
            $state++;
        }
    }

    fclose( $file );
}

$d->close();

?>

  </body>
</html>

Pro práci s databází jsem použil knihovnu dibi a vytvořil jsem tyto dvě tabulky v databázi:

CREATE TABLE `history_nick` (
  `id` int(3) NOT NULL AUTO_INCREMENT,
  `nick` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE `history_msg` (
  `id` int(7) NOT NULL AUTO_INCREMENT,
  `nick_id` int(3) NOT NULL,
  `time` datetime NOT NULL,
  `message` text NOT NULL,
  `chat_with` int(3) NOT NULL,
  PRIMARY KEY (`id`)
);

Soubor config.php obsahuje přístupové údaje pro připojení k databázi:

$connect_params = array(
      'driver'   => 'mysql',
      'host'     => 'localhost',
      'username' => 'root',
      'password' => '',
      'database' => 'chapadlo',
      'charset'  => 'utf8'
  );

Kopete

V Linuxu používám grafické prostředí KDE s klientem Kopete. U něho byla situace lehčí, protože historii zpráv ukládá ve formátu XML, který lze velmi jednoduše zpracovat. Logy jsou uloženy v /home/profil/­.kde/share/ap­ps/kopete/log­s/ a mají vypadají takto:

<!DOCTYPE Kopete-History>
<kopete-history version="0.9" >
 <head>
  <date month="4" year="2009" />
  <contact contactId="martin.grames@gmail.com" type="myself" />
  <contact contactId="en2cs@bot.talk.google.com" />
 </head>
 <msg nick="Martin Grames" in="0" from="martin.grames@gmail.com" time="3 11:40:8" >obtains</msg>
 <msg nick="" in="1" from="en2cs@bot.talk.google.com" time="3 11:40:8" >získá</msg>
 <msg nick="Martin Grames" in="0" from="martin.grames@gmail.com" time="22 9:46:13" >willingness</msg>
 <msg nick="" in="1" from="en2cs@bot.talk.google.com" time="22 9:46:14" >Ochota</msg>
</kopete-history>

Lze vidět, že někdy není vyplněn parametr nick u zprávy, ale to lze vyřešit použitím obsahu parametru from místo něho.

Parser na logy Kopete jsem napsal následovně:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <title>Kopete history parser</title>
  </head>
  <body>

<?php

set_time_limit( 1800 );

define( 'DIR', 'kopete/' );

require_once 'config.php';
require_once 'dibi/dibi.php';

dibi::connect( $connect_params );


$names = array( );

function getNickID( $nick )
{
    global $names;

    foreach ( $names as $name )
    {
        if ( strcmp( $nick, $name[ 'nick' ] ) == 0 ) return $name[ 'id' ];
    }

    $res = dibi::query( 'SELECT [id] FROM [history_nick] WHERE [nick] = %s', $nick );

    if ( $res->rowCount( ) == 0 )
    {
        $arr = array(
            'nick%s' => $nick
        );
        dibi::query( 'INSERT INTO [history_nick]', $arr );

        $id = dibi::insertId( );
    }
    else
    {
        $id = $res->fetchSingle( );
    }

    $tmp = array( 'nick' => $nick, 'id' => $id );
    array_push( $names, $tmp );

    return $id;
}


$d = dir( DIR );

while ( false !== ( $entry = $d->read( ) ) )
{
    if ( $entry[ 0 ] == '.' ) continue;

    $xml = simplexml_load_file( DIR . $entry );

    $m = $xml->head->date[ 'month' ];
    $y = $xml->head->date[ 'year' ];

    // nejdřív naleznu nick od protější strany, abych mohl tímto
    // identifikátorem označit všechny zprávy
    foreach ( $xml->head->contact as $contact )
    {
        if ( empty( $contact[ 'type' ] ) || strcmp( $contact[ 'type' ], 'myself' ) != 0 )
        {
            foreach ( $xml->msg as $msg )
            {
                if ( strcmp( $msg[ 'from' ], $contact[ 'contactId' ] ) == 0 )
                {
                    if ( empty( $msg[ 'nick' ] ) ) $msg[ 'nick' ] = $msg[ 'from' ];

                    $chat_with = getNickID( strval( $msg[ 'nick' ] ) );

                    break;
                }
            }

            break;
        }
    }

    foreach ( $xml->msg as $msg )
    {
        if ( empty( $msg[ 'nick' ] ) ) $msg[ 'nick' ] = $msg[ 'from' ];

        preg_match( '~^([0-9]+)\s+([0-9]+):([0-9]+):([0-9]+)$~', $msg[ 'time' ], $matches );

        $tmp = '';
        for ( $i = 2; $i < 5; $i++ )
        {
            if ( strlen( $matches[ $i ] ) == 1 ) $matches[ $i ] = '0' . $matches[ $i ];
            $tmp .= $matches[ $i ] . ( ( $i < 4 ) ? ':' : '' );
        }

        $time = $matches[ 1 ] . '.' . $m . '.' . $y . ' ' . $tmp;
        echo 'Nick: *' . $msg[ 'nick' ] . "* Time: *" . $time . "* Msg: *"
            . $msg[ 0 ] . "*<br>\n";

        $nick = getNickID( strval( $msg[ 'nick' ] ) );

        $arr = array(
            'nick_id%i' => $nick,
            'time%t' => $time,
            'message%s' => strval( $msg[ 0 ] ),
            'chat_with%i' => $chat_with
        );
        dibi::query( 'INSERT INTO [history_msg]', $arr );
    }

    unset( $xml );
}

$d->close();

?>

  </body>
</html>

Stuktura tabulky je stejná jako v předchozím případě.

Pro další dva klienty jsem již parser nedělal, pouze vás nasměruju a pokud budete mít chuť, můžete se do toho vrhnout úpravou předchozích skriptů.

QIP Infium

Na rozdíl od starého QIPu 2005 tento ukládá historii zpráv šifrovanou, proto opět použijeme plugin a to myHistory. Po instalaci stejně jako u Mirandy ukládá textové soubory průběžně při odeslání/přijetí nové zprávy.

Formát logů je velmi jednoduchý:

-------------------------------------->-
KyberMonty (8:31:22 PM 06/06/2009)
zkus mi tady něco napsat

--------------------------------------<-
Max8 (8:31:31 PM 06/06/2009)
blabla:D

Pidgin

Pidgin mě celkem zklamal, protože jeho logy nejsou moc použitelné. Na začátku sice napíše kdo s kým mluvil, ale to jsou jen identifikátory účtů a já potřeboval přezdívky. Tím může nastat situace, že v logu jsou jen zprávy ode mě a absolutně netuším přezdívku opačné strany – tento záznam historie pak můžu zahodit, protože mi je k ničemu.

Jedině si na to napsat vlastní plugin, pokud již neexistuje nějaký rozumný s lepším výstupním formátem historie zpráv než je tento:

Conversation with 123456789 at St 6. květen 2009, 23:11:43 CEST on 96268388 (icq)
(23:14:46) Vojta: uz spim, mej se
(23:15:13) KyberMonty: ahoj

Závěr

Mé skripty vám mohou být nápomocny při zálohování svých cenných zpráv. Nejdříve je potřeba logy zkopírovat do příslušného adresáře, kde se nachází skript (nebo na adresář s logy nasměrovat symbolický odkaz). Pak skript spustit a zpracované logy následně smazat z původního umístění, aby se při dalším zpracování neduplikovaly zprávy. Nebo nic nedělat ručně a jako správný informatik si můžete napsat skript v bashi, který se bude pravidelně spouštět pomocí démona cron (ve Windows to bude dávkový soubor bat, který bude pravidelně spouštět plánovač úloh).

Další varianta je používat jenom Jabber – u Google Talk a nově taky u Jabbim.cz se ukládají všechny zprávy na jejich serverech. V reálném životě ale narazíte na několik přátel s jinou sítí a budete muset použít nějaký transport, který není úplně nejlepší řešení a ne vždy funguje tak, jak bychom chtěli.

A jako třetí variantu můžete využít služeb serverů IM history a Dexrex, které nabízejí ukládání historie zpráv z různých klientů na jejich serverech. Tyto služby jsem objevil až dnes při procházení pluginů do Pidginu, takže absolutně netuším do jaké míry jsou funkční a spolehlivé.

Vloženo: 7. 6. 2009 00.31RSS komentářů tohoto článku

Přidat komentář