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ě.
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'
);
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/apps/kopete/logs/ 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ů.
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 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
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.31 | RSS komentářů tohoto článku |