自 动 成 语 接 龙
成语接龙
如题,QQ群里可以加入一个机器人小冰,有一个“成语接龙”的功能,并且在群里经常用于比赛。
那么,我们是否可以做一个自动成语接龙的小东西呢?
答案是可以的。
数据准备
为了完成成语接龙的任务,我们首先需要准备好成语数据。我从网上搞到了一个成语解析文档,通过简单的数据处理之后得到了这样一个文件:
成语文档
这个文档一共有30806行,我们需要将文档读取进来并存储。
我们使用一个泛型列表实现存储。
using System.Collections.Generic;
List<string> idiom = NULL;
然后从指定路径读取文档。
using (StreamReader sr = new StreamReader("E:\\jb.txt"))
{
string t = sr.ReadToEnd();
idiom = new List<string>(t.Split('\n'));
}
现在我们的idiom
列表中就存储了所有常用成语,万事俱备,现在开整!
信息获取
QQ没有任何的接口让我们获取数据,所以我们只能通过模拟鼠标键盘来获取信息。
为了模拟鼠标操作,我们需要设置鼠标的位置。这个功能可以通过SetCursorPos的来实现,将该函数声明到C#中。
[DllImport("User32.dll")]
public extern static void SetCursorPos(int x, int y);
同时我们还需要模拟鼠标拖动等,我们可以通过mouse_event实现,同样声明到C#中。
[DllImport("User32.dll")]
private static extern int mouse_event
(
int dwFlags, int dx, int dy, int dwData, int dwExtraInfo
);
通过计算坐标值,我们可以实现拖动选中消息功能。
附上相应的常量值。
const int MOUSEEVENTF_MOVE = 0x0001;
const int MOUSEEVENTF_LEFTDOWN = 0x0002;
const int MOUSEEVENTF_LEFTUP = 0x0004;
至于是什么意思都能看出来,就不再赘述。
SetCursorPos(58, 869);
Thread.Sleep(150);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Thread.Sleep(150);
mouse_event(MOUSEEVENTF_MOVE, 0, 600, 0, 0);
Thread.Sleep(100);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
Thread.Sleep(100);
之后我们模拟Ctrl+C复制内容。
SendKeys.SendWait("^{C}");
但此时我们不能直接使用剪贴板获取信息,否则会造成消息队列堵塞。此时我们有两种解决方案:造一个文本框Ctrl+V,或者...
当然要或者!Ctrl+V多么麻烦!
我们将SendMessage声明。
public extern static /*LRESULT*/ int SendMessage
(
int* hWnd, uint Msg, int* wParam, int* lParam
);
其中hWnd
代表句柄,你可以想象成身份证号,Msg
代表消息类型,wParam
与lParam
是两个附加参数。
获取承装数据文本框的句柄。
int* hWnd = textBox_Get.Handle
我们要在系统层面进行粘贴,这并不困难,只要告诉系统即可。
const int WM_PASTE = 0x0302;
这就是消息了,我们把它发送给系统。
SendMessage(hWnd,WM_PASTE,0,0);
该函数返回一个LRESULT
类型的值,只要不为零就表示成功,现在我们得到了数据,其中成语使用一对中括号包裹。这是绝佳的条件,我们很快就能从中分割出成语。
string result = "";
result = textBox_Get.Text;
result = result.Split('【')[1];
result = result.Split('】')[0];
然后进行搜索。
string back = "";
list.Remove(result);
foreach (var item in list)
{
if (item.StartsWith(result[result.Length - 1]))
{
back = item;
break;
}
}
list.Remove(back);
现在我们获得了结果变量back
,因为QQ的设定,我们需要恢复必须选中人之后按空格,然而SendKey.SendWait
会堵塞卡死。所以我们需要系统层面的模拟键盘操作。
继续引入API。
[DllImport("user32.dll", SetLastError = true)]
static extern void keybd_event
(
byte bVk, byte bScan, uint dwFlags, uint* dwExtraInfo
);
我们可以使用这个函数轻松的模拟按键,这里给出我们需要的按键(Enter,用于@小冰,Alt,用于发送信息)的值。
const int VK_RETURN = 0x0D;
const int VK_MENU = 0x12;
同时为了粘贴信息到QQ,我们需要获取输入框的句柄,但那很麻烦。于是我们可以曲线救国,先在输入框点击一下(自动),然后获取QQ的句柄就可以了。
想要得到窗口句柄,我们一般使用窗口名字来获得,于是我们又需要引入这样的系统API。
[DllImport("User32.dll")]
public extern static IntPtr FindWindowA
(
string? lpClassName,
string lpWindowName
);
将lpClassName
标记为可空是因为我懒得填(逃)也不用填。该函数返回指定句柄。
现在我们可以轻松的完成下面的部分了。
SetCursorPos(65, 984);
Thread.Sleep(100);
mouse_event(MOUSEEVENTF_LEFTUP | MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Clipboard.SetText($"@小冰");
Thread.Sleep(100);
//或者你的群聊名称
IntPtr QQhWnd = FindWindowA(null, "一个一个盒子的群聊");
SendMessage(QQhWnd,WM_PASTE,new IntPtr(0), new IntPtr(0));
Thread.Sleep(100);
PressKey(Keys.Enter, false);
Thread.Sleep(150);
Clipboard.SetText(back);
mouse_event(MOUSEEVENTF_LEFTUP | MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Thread.Sleep(100);
SendMessage(QQhWnd, WM_PASTE, new IntPtr(0), new IntPtr(0));
Thread.Sleep(100);
keybd_event(VK_ALT, 0, 0, new UIntPtr(0));
keybd_event(0x53,0,0, new UIntPtr(0));
//松开键盘
keybd_event(0x53, 0, KEYEVENTF_KEYUP, new UIntPtr(0));
keybd_event(VK_ALT, 0, KEYEVENTF_KEYUP, new UIntPtr(0));
result = "";
back = "";
//等待下一条
Thread.Sleep(2500);
测试一切成功,没有出现任何问题。
自动发送