最近公司需求 用go写了一个自动模拟按键 添加客户微信的工具
浏览器可以直接使用以下方式来触发自动操作
暂时只适配了 dpi为1.0和1.5
<a href="ShangHao://13333333333">点击添加微信</a>
程序包含多个文件
main.go
package mainimport ("encoding/json""fmt""github.com/lxn/win""io""math""net/http""os""os/exec""os/user""path/filepath""regexp""strconv""strings""syscall""time""unsafe")const VERSION = "0.0.5"type INPUT struct {Type uint32Mi MOUSEINPUT}type MOUSEINPUT struct {Dx int32Dy int32MouseData uint32DwFlags uint32Time uint32DwExtraInfo uintptr}type POSITION struct {AddressX int32AddressY int32NewContactX int32NewContactY int32SearchX int32SearchY int32MessageX int32MessageY int32AddX int32AddY int32ConfirmX int32ConfirmY int32//InputX int32//InputY int32}type program struct{}var (phone stringposition POSITION)const (InputMouse = 0MouseEventFLeftDown = 0x0002MouseEventFLeftUp = 0x0004)func main() {args := os.Args//如果args[1]存在if len(args) > 1 {phone = args[1]//正则去除非数字reg, _ := regexp.Compile("[^0-9]+")phone = reg.ReplaceAllString(phone, "")versionUpgrade(phone)} else {versionUpgrade("")fmt.Println("请输入手机号码")os.Exit(1)}currentUser, _ := user.Current()deskTopPath := filepath.Join(currentUser.HomeDir, "Desktop\\企业微信.lnk")fmt.Printf("打开企业微信:%s\n", deskTopPath)//检查是否存在hwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))if hwnd == 0 {// 未找到窗口cmd := exec.Command("taskkill", "/F", "/IM", "WXWork.exe")cmd.Run()code, err := open(deskTopPath)if code != 1 {fmt.Print(err)}//打开企业微信成功time.Sleep(5 * time.Second)}hwnd = win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))if hwnd == 0 {fmt.Println("未找到企业微信窗口")time.Sleep(5 * time.Second)return}top := win.GetForegroundWindow()if top != 0 {win.ShowWindow(top, win.SW_MINIMIZE)}dpi := getDpi(hwnd)fmt.Printf("屏幕的dpi:%.2f\n", dpi)var err errorposition, err = getPosition(dpi)if err != nil {fmt.Println(err)time.Sleep(5 * time.Second)os.Exit(1)}win.SendMessage(hwnd, win.WM_CLOSE, 0, 0)//win.ShowWindow(hwnd, win.SW_RESTORE) // 恢复窗口大小min()open(deskTopPath)//获取企业微信路径//开始点击start()if top != 0 {win.ShowWindow(top, win.SW_RESTORE) // 恢复窗口大小}//检查是否有浏览器窗口}func min() {screenWidth := int32(win.GetSystemMetrics(win.SM_CXSCREEN))screenHeight := int32(win.GetSystemMetrics(win.SM_CYSCREEN))fmt.Println(screenWidth, screenHeight)mouseMove(int(screenWidth), int(screenHeight))}// 获取设备dpifunc getDpi(hwnd win.HWND) float64 {err := setDpiAwareness()if err != nil {fmt.Println("无法设置 DPI 感知模式:", err)return 0}factor, err := getScalingFactor(hwnd)if err != nil {return 0}return factor//fmt.Printf("缩放比例: %.2f\n", factor)}// 打开企业微信func open(path string) (int, string) {cmd := exec.Command("cmd", "/C", "start", path)//判断文件是否存在_, err := os.Stat(path)fmt.Print(err)if err != nil {return 0, err.Error()}err = cmd.Run()if err != nil {fmt.Println("Error opening shortcut:", err)return 0, err.Error()}return 1, "success"}func mouseMove(x int, y int) {//循环,保持鼠标位置win.SetCursorPos(int32(x), int32(y))for {var mousePos win.POINTwin.GetCursorPos(&mousePos)if !win.GetCursorPos(&mousePos) {// 获取鼠标位置失败break}//fmt.Printf("鼠标位置:%d,%d,目标位置:%d,%d\n", mousePos.X, mousePos.Y, x, y)// 获取鼠标位置if math.Abs(float64(x)-float64(mousePos.X)) <= 2 && math.Abs(float64(y)-float64(mousePos.Y)) <= 2 {// 鼠标位置正确,执行点击操作inputDown := INPUT{Type: InputMouse,Mi: MOUSEINPUT{DwFlags: MouseEventFLeftDown,},}win.SendInput(1, unsafe.Pointer(&inputDown), int32(unsafe.Sizeof(inputDown)))break}win.SetCursorPos(int32(x), int32(y))}// 等待一段时间,模拟按下持续时间time.Sleep(100 * time.Millisecond)for {var mousePos win.POINTwin.GetCursorPos(&mousePos)if !win.GetCursorPos(&mousePos) {// 获取鼠标位置失败break}// 获取鼠标位置if math.Abs(float64(x)-float64(mousePos.X)) <= 2 && math.Abs(float64(y)-float64(mousePos.Y)) <= 2 {// 鼠标位置正确,执行点击操作// 模拟鼠标左键释放inputUp := INPUT{Type: InputMouse,Mi: MOUSEINPUT{DwFlags: MouseEventFLeftUp,},}win.SendInput(1, unsafe.Pointer(&inputUp), int32(unsafe.Sizeof(inputUp)))break}win.SetCursorPos(int32(x), int32(y))}time.Sleep(500 * time.Millisecond)}func GetHwnd(hwnd win.HWND) win.HWND {//var p win.POINT//win.GetCursorPos(&p)//hwnd := win.WindowFromPoint(p)for win.GetParent(hwnd) != 0 {hwnd = win.GetParent(hwnd)}return hwnd}func topWindow(hwnd win.HWND, windowWidth int32, windowHeight int32) (int32, int32, int32, int32) {//hwnd = GetHwnd(hwnd)// 获取屏幕的宽度和高度screenWidth := int32(win.GetSystemMetrics(win.SM_CXSCREEN))screenHeight := int32(win.GetSystemMetrics(win.SM_CYSCREEN))// 计算窗口的位置和大小//windowWidth := int32(1539) // 设置窗口宽度//windowHeight := int32(1000) // 设置窗口高度windowX := int32(screenWidth-windowWidth) / 2 // 计算窗口的左上角X坐标windowY := int32(screenHeight-windowHeight) / 2 // 计算窗口的左上角Y坐标win.SetForegroundWindow(hwnd) // 将窗口置于前台////////win.SetWindowPos(hwnd, win.HWND_TOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE)time.Sleep(500 * time.Millisecond)win.MoveWindow(hwnd, windowX, windowY, windowWidth, windowHeight, true) // 移动窗口位置和大小// 设置窗口位置和大小////////////////////////////////////////////////////////////////// win.AnimateWindow(hwnd, 1, win.AW_HIDE|win.AW_CENTER)// //win.AnimateWindow(hwnd, 1, win.AW_CENTER)//// time.Sleep(500 * time.Millisecond)//// win.SetWindowPos(hwnd, win.HWND_TOPMOST, windowX, windowY, windowWidth, windowHeight, win.SWP_NOMOVE|win.SWP_NOSIZE)//// win.MoveWindow(hwnd, windowX, windowY, windowWidth, windowHeight, true)////win.SetForegroundWindow(hwnd) // 将窗口置于前台//// win.AnimateWindow(hwnd, 700, win.AW_BLEND) // 渐显//// time.Sleep(500 * time.Millisecond)return windowX, windowY, windowWidth, windowHeight}func start() {fmt.Printf("搜索企业微信窗口句柄...\n")hwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))if hwnd == 0 {// 未找到窗口fmt.Printf("搜索失败,未找到窗口句柄。\n")return}//窗口置顶 并居中固定大小windowsX, windowsY, _, _ := topWindow(hwnd, 1539, 1000)// 点击通讯录 40-654mouseMove(int(windowsX+position.AddressX), int(windowsY+position.AddressY))////点击添加新联系人mouseMove(int(windowsX+position.NewContactX), int(windowsY+position.NewContactY))//点击添加联系人 弹出搜索框mouseMove(int(windowsX+position.SearchX), int(windowsY+position.SearchY))time.Sleep(1 * time.Second)searchHwnd := win.FindWindow(syscall.StringToUTF16Ptr("SearchExternalsWnd"), nil)if searchHwnd == 0 {// 未找到窗口fmt.Printf("搜索外部联系人添加窗口句柄失败。\n")restore(windowsX, windowsY)return}fmt.Printf("搜索外部联系人添加窗口句柄成功...\n")fmt.Printf("输入字符...\n")//win.SetForegroundWindow(searchHwnd) // 将窗口置于前台time.Sleep(500 * time.Millisecond)searchX, searchY, _, _ := topWindow(searchHwnd, 600, 462)win.SetFocus(searchHwnd)//time.Sleep(500 * time.Millisecond)//mouseMove(int(windowsX+position.InputX), int(windowsY+position.InputY))fmt.Printf("点击输入框...\n")// 循环发送每个字符的键盘消息for _, c := range phone {win.SendMessage(searchHwnd, win.WM_CHAR, uintptr(c), 0)}// 发送Enter键消息win.SendMessage(searchHwnd, win.WM_KEYDOWN, uintptr(win.VK_RETURN), 0)win.SendMessage(searchHwnd, win.WM_KEYUP, uintptr(win.VK_RETURN), 0)time.Sleep(1000 * time.Millisecond)win.SetForegroundWindow(searchHwnd) // 将窗口置于前台time.Sleep(500 * time.Millisecond)//点击添加mouseMove(int(searchX+position.AddX), int(searchY+position.AddY))time.Sleep(500 * time.Millisecond)//添加确认框句柄搜索inputReasonWndHwnd := win.FindWindow(syscall.StringToUTF16Ptr("InputReasonWnd"), nil)if inputReasonWndHwnd == 0 {// 未找到窗口fmt.Printf("搜索添加确认框句柄搜索失败。\n")restore(windowsX, windowsY)return}//inputReasonX, inputReasonY, _, _ := topWindow(inputReasonWndHwnd, 589, 366)time.Sleep(500 * time.Millisecond)mouseMove(int(searchX+position.ConfirmX), int(searchY+position.ConfirmY))restore(windowsX, windowsY)}// 恢复func restore(windowsX, windowsY int32) {notifyHwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkMessageBoxFrame"), nil)if notifyHwnd != 0 {win.PostMessage(notifyHwnd, win.WM_CLOSE, 0, 0)}searchHwnd := win.FindWindow(syscall.StringToUTF16Ptr("SearchExternalsWnd"), nil)if searchHwnd != 0 {// 发送关闭消息win.SendMessage(searchHwnd, win.WM_CLOSE, 0, 0)}time.Sleep(500 * time.Millisecond)////重新点回消息列表//mouseMove(int(windowsX+position.MessageX), int(windowsY+position.MessageY))////time.Sleep(500 * time.Millisecond)hwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))if hwnd == 0 {// 未找到窗口fmt.Printf("搜索失败,未找到窗口句柄。\n")return}win.SendMessage(hwnd, win.WM_CLOSE, 0, 0)//win.ShowWindow(hwnd, win.SW_RESTORE) // 恢复窗口大小//win.ShowWindow(hwnd, win.SW_MINIMIZE)//检查是否有提示框,如果有关闭它}type version struct {Version string `json:"version"`DownloadUrl string `json:"downloadUrl"`}func compareVersion(version1 string, version2 string) int {var res int// 通过字符串切割会生成一个数组ver1Strs := strings.Split(version1, ".") //[1 2 3]ver2Strs := strings.Split(version2, ".") //[2 3 4 5]ver1Len := len(ver1Strs)ver2Len := len(ver2Strs)//fmt.Println(ver1Strs, ver2Strs)verLen := ver1Lenif len(ver1Strs) < len(ver2Strs) {verLen = ver2Len}for i := 0; i < verLen; i++ {var ver1Int, ver2Int intif i < ver1Len {// 字符串转换成整数strconv.Atoiver1Int, _ = strconv.Atoi(ver1Strs[i])}if i < ver2Len {ver2Int, _ = strconv.Atoi(ver2Strs[i])}if ver1Int < ver2Int {res = -1break}if ver1Int > ver2Int {res = 1break}}return res}func versionUpgrade(phone string) {url := "https://xxxx/version.json"fmt.Printf("当前版本:%s\n", VERSION)resp, err := http.Get(url)if err != nil {fmt.Printf("获取版本失败:%s\n", err)}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {fmt.Printf("读取版本失败:%s\n", err)}var data versionerr = json.Unmarshal(body, &data)if err != nil {fmt.Printf("解析版本失败:%s\n", err)return}fmt.Printf("当前版本:%s,远程版本:%s\n", VERSION, data.Version)//检查本地版本是否小于远程版本if compareVersion(VERSION, data.Version) >= 0 {return}fmt.Printf("需要更新\n")//更新file, err := http.Get(data.DownloadUrl)if err != nil {fmt.Printf("访问下载连接失败:%s\n", err)return}defer file.Body.Close()fileBytes, err := io.ReadAll(file.Body)if err != nil {fmt.Printf("更新失败:%s\n", err)return}path, _ := os.Executable()path = filepath.Dir(path)dir := filepath.Join(path, "version")fmt.Println(dir)_ = os.Mkdir(dir, 0644)err = os.WriteFile(filepath.Join(dir, "tmp.exe"), fileBytes, 0644)if err != nil {fmt.Printf("保存失败:%s\n", err)return}fmt.Printf("保存成功\n")var cmd *exec.CmdupgradePath := filepath.Join(path, "Upgrade.exe")if phone != "" {cmd = exec.Command("cmd.exe", "/C", "start", "/B", upgradePath, phone)} else {cmd = exec.Command("cmd.exe", "/C", "start", "/B", upgradePath)}if err = cmd.Start(); err != nil {fmt.Println(err)}fmt.Println("拉起更新程序")os.Exit(0)}
position.go
package mainimport ("errors")func getPosition(dpi float64) (POSITION, error) {if dpi == 1.0 {return POSITION{33, 445, 150, 80, 1473, 40, 33, 92, 449, 212, 300, 270}, nil} else if dpi == 1.5 {return POSITION{40, 654, 240, 120, 1470, 60, 50, 130, 520, 210, 300, 311}, nil}//抛出错误return POSITION{}, errors.New("dpi must be 1.0 or 1.5")}
windows.go
package mainimport ("github.com/lxn/win""syscall""unsafe")var (user32 = syscall.NewLazyDLL("user32.dll")shcore = syscall.NewLazyDLL("shcore.dll")getDpiForWindow = user32.NewProc("GetDpiForWindow")getProcessDpiAwareness = shcore.NewProc("GetProcessDpiAwareness")setProcessDpiAwareness = shcore.NewProc("SetProcessDpiAwareness")processPerMonitorDpiAwareness = 2procBlockInput = user32.NewProc("BlockInput"))func getDpiAwareness() (int, error) {var awareness uintptrr, _, err := getProcessDpiAwareness.Call(uintptr(0), uintptr(unsafe.Pointer(&awareness)))if r != 0 {return 0, err}return int(awareness), nil}func setDpiAwareness() error {r, _, err := setProcessDpiAwareness.Call(uintptr(processPerMonitorDpiAwareness))if r != 0 {return err}return nil}func getScalingFactor(hwnd win.HWND) (float64, error) {awareness, err := getDpiAwareness()if err != nil {return 0, err}if awareness == processPerMonitorDpiAwareness {hdc := win.GetDC(hwnd)defer win.ReleaseDC(hwnd, hdc)dpi := win.GetDeviceCaps(hdc, win.LOGPIXELSX)return float64(dpi) / 96.0, nil}hwndDpi, _, _ := getDpiForWindow.Call(uintptr(hwnd))return float64(hwndDpi) / 96.0, nil}
上面程序打包得到 ShangHao.exe
与之配套的,还有一个Upgrade程序
package mainimport ("fmt""os""os/exec""path/filepath""time")func main() {fmt.Println("upgrade....")fmt.Println("rename:")path, err := os.Executable()if err != nil {return}path = filepath.Dir(path)dir := filepath.Join(path, "version")err = os.Rename(filepath.Join(dir, "tmp.exe"), filepath.Join(path, "ShangHao.exe"))if err != nil {return}fmt.Println(dir, path)if err != nil {return}fmt.Println("done")time.Sleep(1 * time.Second)args := os.Argsif len(args) > 1 {phone := args[1]var cmd *exec.Cmdshanghao := filepath.Join(path, "ShangHao.exe")if phone != "" {cmd = exec.Command("cmd.exe", "/C", "start", shanghao, phone)} else {cmd = exec.Command("cmd.exe", "/C", "start", shanghao)}err := cmd.Start()if err != nil {return}} else {os.Exit(0)}//重新吊起源程序}
打包得到Upgrade.exe
将两个文件放到一起,放到任意目录下,并创建一个批处理文件,管理员权限执行批处理文件
@echo offsetlocalset "script_dir=%~dp0"reg delete HKEY_CLASSES_ROOT\ShangHao /fecho %errorlevel%reg add HKEY_CLASSES_ROOT\ShangHao /ve /d "URL:ShangHao Protocol Handler" /fecho %errorlevel%reg add HKEY_CLASSES_ROOT\ShangHao /v "URL Protocol" /t REG_SZ /d "" /fecho %errorlevel%reg add HKEY_CLASSES_ROOT\ShangHao\DefaultIcon /ve /d "%script_dir%ShangHao.exe" /fecho %errorlevel%reg add HKEY_CLASSES_ROOT\ShangHao\shell /ve /fecho %errorlevel%reg add HKEY_CLASSES_ROOT\ShangHao\shell\open /ve /fecho %errorlevel%reg add HKEY_CLASSES_ROOT\ShangHao\shell\open\command /ve /d "\"%script_dir%ShangHao.exe\" \"%%1\"" /fecho %errorlevel%endlocal
脚本执行完成后 就可以在浏览器直接调用exe并传参了
比如 企业微信添加1333333333这个手机号对应的微信
<a href="ShangHao://13333333333">点击添加微信</a>