说明

如题 是个蛋疼物。目前QQ的聊天记录导出功能三种聊天记录格式的导出 很让人郁闷

  1. TXT 没图
  2. BAK 只能再导入QQ使用
  3. MHT 有图有字,缺点是一旦聊天记录很多,文件体积就会很大,几乎所有的工具都不能正常打开

单纯的把MHT转换成HTML也不行,因为HTML也很大,加上图片之类的资源 也会卡死 于是只能切开显示,处理思路很简单,就是超大的文本文件,按行顺序处理,把图片解码存入文件,然后分割HTML内容 代码如下 只支持单个QQ群导出记录

<?php
ini_set('pcre.backtrack_limit',1000000);
$is_table_end = false ; $page = 0 ;$contents = ''; $output_zip = './test.zip';
#判断输入文件
if(!file_exists($argv[1]))
{
    echo 'There isn\'t have this file.';
    exit;
}
#建立归档文件 默认为
make_output_target($output_zip);
$handle = fopen($argv[1], "rb");
#分段处理
do
{
    $contents .=fread($handle,124416);#wtf
    $contents = mht_process($contents,$output_zip);
}
while (!feof($handle));


#主体数据处理
function mht_process($contents,$output_zip){
    Global $is_table_end;
    Global $page;
    $zip = new ZipArchive;
    if ($zip->open($output_zip,ZIPARCHIVE::CREATE) !== TRUE) 
    {
        echo 'create image failed';
        exit;
    }
    #判断非图片消息部分是否处理完毕
    if(false === $is_table_end)
    {
        $html_head = '<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>QQ Message</title><style type="text/css">body{font-size:12px; line-height:22px; margin:2px;}td{font-size:12px; line-height:22px;}</style></head><body><table width=100% cellspacing=0>';
        $html_foot = '</table></body></html>';
        #判断消息部分是否完毕 只处理图片部分
        if(false !== strpos($contents,$html_foot))
        {
            $is_table_end = true; 
        }
        $r = preg_match_all ('|<tr.*?\</tr\>|ims',  $contents , $matches ,PREG_OFFSET_CAPTURE);
        if($r)
        {
            $matches = array_chunk($matches[0],200);
            foreach($matches as $key=>$val)
            {
                $arr = array_column($val,0);
                array_walk( $arr , function(&$v, $k) use ($zip)
                {
                    $v = preg_replace('|<IMG src="{(\S)(\S)(\S+).dat|ims','<IMG src="../images/$1/$2/{$1$2$3.dat',$v);
                });
                $zip->addFromString('messages/'.sprintf("%08d", $page+$key).'.html' ,$html_head . implode('',$arr).'<td> <H1><a href="./'.sprintf("%08d", $page+$key-1).'.html">Prev page</a></h1> <H1><a href="./'.sprintf("%08d", $page+$key+1).'.html">Next page</a></h1></td>'.$html_foot);
            }
            $page += $key; 
            $pos = end($val);
            $contents = substr($contents,bcadd($pos[1],strlen($pos[0]),0));
        }
        else
        {
            $contents = '';
        }
    }
    #处理图片部分
    if(true === $is_table_end)
    {
        #图片数据匹配
        $r = preg_match_all ('|Content-Type:image.*?:base64.*?Content-Location:(.*?)\.dat(.*?)(?:------=_)|ims',  $contents , $matches ,PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
        if($r)
        {
            //$matches = array_chunk($matches[0],200);
            $result = array();
            foreach($matches as $key=>$val){
                $result = array('name'=>$val[1][0],'contents'=>$val[2][0]);
                $dir = 'images/'. substr($result['name'],1,1) .'/' . substr($result['name'],2,1) .'/'.$result['name'] . '.dat';
                #写入图片表情文件到硬盘
                $zip->addFromString($dir,base64_decode(trim($result['contents'])));
                $result = array();
            }
            $result = array();
            $contents = substr($contents,$val[0][1]); 
        }
        else
        {
            $contents = '';
        }
    }
    $zip->close();
    #剩余部分返回 下一次处理拼接数据
    return $contents;
}

#建立保存目标
function make_output_target($output='./test.zip')
{
    $zip = new ZipArchive;
    if ($zip->open($output,ZIPARCHIVE::CREATE) !== TRUE) 
    {
        echo 'create images directory failed';
        exit;
    }
    $zip->addEmptyDir('images');
    $tmp = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
    array_walk($tmp,function($val,$key) use ($tmp,$zip)
    {
        $base_path = 'images/'.$val;
        $zip->addEmptyDir($base_path);
        array_walk($tmp,function($val,$key) use ($tmp,$zip,$base_path)
        {
            $zip->addEmptyDir($base_path.'/'.$val);
        });
    });
    $zip->addEmptyDir('messages');
    $zip->close();
    return 0;
}

使用方法

"php.exe" cli.php input.mht > log.txt
pause

打包环境+代码part01

打包环境+代码part02

打包环境+代码part03

打包环境+代码part04

生成 ZIP 文件之后 可以用 winmount 来挂载查看 无需解压很方便