目录
前言
提示: 本文仅用于学习分享,禁止用于任何违法行为,如有侵权,请联系删除。
如题,本篇文章将对TIM消息进行实时采集,优点简单易懂,不需要了解汇编与逆向,基本没有封号风险,缺点是占用键盘鼠标,下面直接上演示图片
一、采集思路是什么?
看了前言的GIF图的小伙伴不难看出直接在聊天窗中采集到了聊天数据,包括有时间,发送人,以及内容,最关键的思路是利用了剪切板
当我们在聊天窗使用ctrl+a+c,然后在发送框中粘贴,会发现粘贴的内容是一种特殊的格式进行显示,可以判断出剪切板中可以拿到聊天的数据内容
这里通过代码对剪切板内的内容进行分析,得到8个格式化的类型,对这8个类型逐一分析,举例其中Text格式化方式可以拿到聊天数据,但是缺少了图片的信息,经过我逐一的测试,最终 QQ_Msg_RichEdit_Format 的格式方式可以拿到比较完善的数据,感兴趣的小伙伴可以自己尝试一下。那最核心的采集部分就完结了。
二、怎么样使用软件去实时进行采集?
知道了核心的采集步骤,我们的采集思路是 模拟人工去窗口执行全选复制的操作->软件解析剪切板聊天内容->数据进行清洗输出到可视化页面
1.模拟人工去窗口执行全选复制的操作
1) 第一步我们需要拿到聊天窗口的句柄,通过窗口名称或者进程ID进行获取,这里要注意,一定要把聊天窗口双击点出单独的页面来!
代码如下(示例):
IntPtr FindTarget(string monitorContent)
{
var pid = 0;
IntPtr hwndRel = IntPtr.Zero;
int.TryParse(monitorContent, out pid);
if (pid > 0)
{
hwndRel = Process.GetProcessById(pid).MainWindowHandle;
}
if (pid <= 0 || hwndRel == null || hwndRel == IntPtr.Zero)
{
hwndRel = WindowsAPI.FindWindow(null, monitorContent);
}
return hwndRel;
}
模拟点击,模拟全选复制,代码如下(示例):
WindowsAPI.SetForegroundWindow(hwnd);//置顶聊天窗
RECT rECT = new RECT();
WindowsAPI.GetWindowRect(hwnd, ref rECT);//获取句柄位置
//获取聊天窗内容 偏移点击
var x = rECT.left + 250;
var y = rECT.top + 150;
WindowsAPI.SetCursorPos(x, y);//设置鼠标位置
WindowsAPI.mouse_event(WindowsAPI.MouseEventFlag.LeftDown, 0, 0, 0, 0); //模拟鼠标按下操作
WindowsAPI.mouse_event(WindowsAPI.MouseEventFlag.LeftUp, 0, 0, 0, 0);//模拟鼠标放开操作
//全选复制操作
WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.CTRL_key, 0, 0, 0);
WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.A_key, 0, 0, 0);
WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.A_key, 0, 2, 0);
WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.C_key, 0, 0, 0);
WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.C_key, 0, 2, 0);
WindowsAPI.keybd_event(WindowsAPI.VirtualKeyCode.CTRL_key, 0, 2, 0);
2.软件解析剪切板聊天内容
代码如下(示例):
IDataObject data = Clipboard.GetDataObject();
if (data != null)
{
var stream = (Stream)data.GetData("QQ_Unicode_RichEdit_Format", false);
using (var streamReader = new StreamReader(stream))
{
var str = streamReader.ReadToEnd();
var md5 = GetMD5(str);
var dto = new UpcomingMessageDTO
{
Hwnd = hwnd,
Message = str,
MD5 = md5
};
if (UpcomingMessage.Contains(dto) == false)
{
UpcomingMessage.Enqueue(dto);
}
}
stream.Dispose();
stream.Close();
}
3.数据清洗输出到可视化页面
通过以上步骤从剪切板内容拿到了一串XML数据
处理比较麻烦,我把这一串XML数据转成了Json数据
从这串Json中不难发现聊天内容已经可以拿到,但是有不同的消息内容,@type这个字段主要标识消息内容的,经过测试分析0为文本内容,1为图片,并且聊天内容会有多条拼接的情况,我们需要对拼接的数据进行拆解分析。我们拿出某一个拼接的聊天记录,利用正则进行匹配输出,如图
最后通过正则 (\S+)\s([0-9]{2}:[0-5][0-9]:[0-5][0-9])\r\n(.+|.*?) 拿到了匹配的消息内容,当然这个正则可能还有点问题,有更严谨的小伙伴自行进行测试分析了,通过正则分组拿到了昵称,时间,内容,再通过时间以及昵称进行去重,就可以在页面上呈现出来了。
代码如下(示例):
/// <summary>
/// 数据清洗
/// </summary>
void ProcessMessage()
{
Task.Factory.StartNew(async () =>
{
while (true)
{
try
{
if (UpcomingMessage.Count > 0)
{
var message = UpcomingMessage.Dequeue();
//xml数据转json
var doc = new XmlDocument();
doc.LoadXml(message.Message);
string json = Newtonsoft.Json.JsonConvert.SerializeXmlNode(doc);
//反序列化json数据
var timMessage = Newtonsoft.Json.JsonConvert.DeserializeObject<TimMessage>(json);
var length = timMessage.QQRichEditFormat.EditElement.Count;
if (length > 0)
{
var outTimMessage = new List<OutTimMessage>();
for (int i = 0; i < length; i++)
{
var lastMsgItem = outTimMessage.LastOrDefault();
var item = timMessage.QQRichEditFormat.EditElement[i];
if (item != null)
{
if (item.type == "1" || item.type == "2" || item.type == "6")//图片
{
if (lastMsgItem != null)
{
lastMsgItem.SendMessage = lastMsgItem.SendMessage + $"【图片{item.filepath}】";
}
}
else if (item.type == "0")//消息
{
//正则拆分消息
var msgs = Regex.Matches(item.cdata_section, _msgRegexStr);
if (msgs?.Count > 0)
{
foreach (Match msg in msgs)
{
var index = msg.Index;
var valueLen = msg.Value.Length;
var sender = msg.Groups[1].Value;
var time = msg.Groups[2].Value;
var sendMessage = msg.Groups[3].Value;
if (i == 0 && index > 0 && lastMsgItem != null)//消息拼接
{
lastMsgItem.SendMessage = lastMsgItem.SendMessage + sendMessage;
}
else
{
if (Message.Where(x => x.Hwnd == message.Hwnd && x.SendTime == time).Any() == false)
{
outTimMessage.Add(new OutTimMessage
{
SendTime = time,
Hwnd = message.Hwnd,
SendMessage = sendMessage,
Sender = sender
});
}
}
}
}
}
else
{
Console.WriteLine($"暂未支持解析类型{Newtonsoft.Json.JsonConvert.SerializeObject(item)}");
}
}
}
Message.AddRange(outTimMessage);
foreach (var item in outTimMessage)
{
//输出消息
AppandLogs($"收到消息:{item.Sender},{item.SendTime},{item.SendMessage}", LogLevel.Info);
}
}
}
}
catch (Exception ex)
{
AppandLogs("消息处理出错!", LogLevel.Error);
return;
}
await Task.Delay(1000);
}
}, TaskCreationOptions.LongRunning);
}
这里数据去重采用了正序,更好的方案应该采用逆序进行去重更加高效,感兴趣的朋友可自行摸索测试了!
以上用到的WinApi链接:Windows API
下面附上懒人链接:资源地址
总结
从本篇文章可以了解到TIM聊天信息的采集思路,以及数据的清洗去重展示,那同样QQ也是同样的操作方式,甚至还可以做到抢红包,消息回复等,更多功能交给小伙伴们自行挖掘了!