用go实现一个 企业微信自动加微信的工具

2023-07-19 09:05:49   工作备份

  go,浏览器调用,模拟按键操作  

最近公司需求 用go写了一个自动模拟按键 添加客户微信的工具
浏览器可以直接使用以下方式来触发自动操作

暂时只适配了 dpi为1.0和1.5

  1. <a href="ShangHao://13333333333">点击添加微信</a>

程序包含多个文件
main.go

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/lxn/win"
  6. "io"
  7. "math"
  8. "net/http"
  9. "os"
  10. "os/exec"
  11. "os/user"
  12. "path/filepath"
  13. "regexp"
  14. "strconv"
  15. "strings"
  16. "syscall"
  17. "time"
  18. "unsafe"
  19. )
  20. const VERSION = "0.0.5"
  21. type INPUT struct {
  22. Type uint32
  23. Mi MOUSEINPUT
  24. }
  25. type MOUSEINPUT struct {
  26. Dx int32
  27. Dy int32
  28. MouseData uint32
  29. DwFlags uint32
  30. Time uint32
  31. DwExtraInfo uintptr
  32. }
  33. type POSITION struct {
  34. AddressX int32
  35. AddressY int32
  36. NewContactX int32
  37. NewContactY int32
  38. SearchX int32
  39. SearchY int32
  40. MessageX int32
  41. MessageY int32
  42. AddX int32
  43. AddY int32
  44. ConfirmX int32
  45. ConfirmY int32
  46. //InputX int32
  47. //InputY int32
  48. }
  49. type program struct{}
  50. var (
  51. phone string
  52. position POSITION
  53. )
  54. const (
  55. InputMouse = 0
  56. MouseEventFLeftDown = 0x0002
  57. MouseEventFLeftUp = 0x0004
  58. )
  59. func main() {
  60. args := os.Args
  61. //如果args[1]存在
  62. if len(args) > 1 {
  63. phone = args[1]
  64. //正则去除非数字
  65. reg, _ := regexp.Compile("[^0-9]+")
  66. phone = reg.ReplaceAllString(phone, "")
  67. versionUpgrade(phone)
  68. } else {
  69. versionUpgrade("")
  70. fmt.Println("请输入手机号码")
  71. os.Exit(1)
  72. }
  73. currentUser, _ := user.Current()
  74. deskTopPath := filepath.Join(currentUser.HomeDir, "Desktop\\企业微信.lnk")
  75. fmt.Printf("打开企业微信:%s\n", deskTopPath)
  76. //检查是否存在
  77. hwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))
  78. if hwnd == 0 {
  79. // 未找到窗口
  80. cmd := exec.Command("taskkill", "/F", "/IM", "WXWork.exe")
  81. cmd.Run()
  82. code, err := open(deskTopPath)
  83. if code != 1 {
  84. fmt.Print(err)
  85. }
  86. //打开企业微信成功
  87. time.Sleep(5 * time.Second)
  88. }
  89. hwnd = win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))
  90. if hwnd == 0 {
  91. fmt.Println("未找到企业微信窗口")
  92. time.Sleep(5 * time.Second)
  93. return
  94. }
  95. top := win.GetForegroundWindow()
  96. if top != 0 {
  97. win.ShowWindow(top, win.SW_MINIMIZE)
  98. }
  99. dpi := getDpi(hwnd)
  100. fmt.Printf("屏幕的dpi:%.2f\n", dpi)
  101. var err error
  102. position, err = getPosition(dpi)
  103. if err != nil {
  104. fmt.Println(err)
  105. time.Sleep(5 * time.Second)
  106. os.Exit(1)
  107. }
  108. win.SendMessage(hwnd, win.WM_CLOSE, 0, 0)
  109. //
  110. win.ShowWindow(hwnd, win.SW_RESTORE) // 恢复窗口大小
  111. min()
  112. open(deskTopPath)
  113. //获取企业微信路径
  114. //开始点击
  115. start()
  116. if top != 0 {
  117. win.ShowWindow(top, win.SW_RESTORE) // 恢复窗口大小
  118. }
  119. //检查是否有浏览器窗口
  120. }
  121. func min() {
  122. screenWidth := int32(win.GetSystemMetrics(win.SM_CXSCREEN))
  123. screenHeight := int32(win.GetSystemMetrics(win.SM_CYSCREEN))
  124. fmt.Println(screenWidth, screenHeight)
  125. mouseMove(int(screenWidth), int(screenHeight))
  126. }
  127. // 获取设备dpi
  128. func getDpi(hwnd win.HWND) float64 {
  129. err := setDpiAwareness()
  130. if err != nil {
  131. fmt.Println("无法设置 DPI 感知模式:", err)
  132. return 0
  133. }
  134. factor, err := getScalingFactor(hwnd)
  135. if err != nil {
  136. return 0
  137. }
  138. return factor
  139. //fmt.Printf("缩放比例: %.2f\n", factor)
  140. }
  141. // 打开企业微信
  142. func open(path string) (int, string) {
  143. cmd := exec.Command("cmd", "/C", "start", path)
  144. //判断文件是否存在
  145. _, err := os.Stat(path)
  146. fmt.Print(err)
  147. if err != nil {
  148. return 0, err.Error()
  149. }
  150. err = cmd.Run()
  151. if err != nil {
  152. fmt.Println("Error opening shortcut:", err)
  153. return 0, err.Error()
  154. }
  155. return 1, "success"
  156. }
  157. func mouseMove(x int, y int) {
  158. //循环,保持鼠标位置
  159. win.SetCursorPos(int32(x), int32(y))
  160. for {
  161. var mousePos win.POINT
  162. win.GetCursorPos(&mousePos)
  163. if !win.GetCursorPos(&mousePos) {
  164. // 获取鼠标位置失败
  165. break
  166. }
  167. //fmt.Printf("鼠标位置:%d,%d,目标位置:%d,%d\n", mousePos.X, mousePos.Y, x, y)
  168. // 获取鼠标位置
  169. if math.Abs(float64(x)-float64(mousePos.X)) <= 2 && math.Abs(float64(y)-float64(mousePos.Y)) <= 2 {
  170. // 鼠标位置正确,执行点击操作
  171. inputDown := INPUT{
  172. Type: InputMouse,
  173. Mi: MOUSEINPUT{
  174. DwFlags: MouseEventFLeftDown,
  175. },
  176. }
  177. win.SendInput(1, unsafe.Pointer(&inputDown), int32(unsafe.Sizeof(inputDown)))
  178. break
  179. }
  180. win.SetCursorPos(int32(x), int32(y))
  181. }
  182. // 等待一段时间,模拟按下持续时间
  183. time.Sleep(100 * time.Millisecond)
  184. for {
  185. var mousePos win.POINT
  186. win.GetCursorPos(&mousePos)
  187. if !win.GetCursorPos(&mousePos) {
  188. // 获取鼠标位置失败
  189. break
  190. }
  191. // 获取鼠标位置
  192. if math.Abs(float64(x)-float64(mousePos.X)) <= 2 && math.Abs(float64(y)-float64(mousePos.Y)) <= 2 {
  193. // 鼠标位置正确,执行点击操作
  194. // 模拟鼠标左键释放
  195. inputUp := INPUT{
  196. Type: InputMouse,
  197. Mi: MOUSEINPUT{
  198. DwFlags: MouseEventFLeftUp,
  199. },
  200. }
  201. win.SendInput(1, unsafe.Pointer(&inputUp), int32(unsafe.Sizeof(inputUp)))
  202. break
  203. }
  204. win.SetCursorPos(int32(x), int32(y))
  205. }
  206. time.Sleep(500 * time.Millisecond)
  207. }
  208. func GetHwnd(hwnd win.HWND) win.HWND {
  209. //var p win.POINT
  210. //win.GetCursorPos(&p)
  211. //hwnd := win.WindowFromPoint(p)
  212. for win.GetParent(hwnd) != 0 {
  213. hwnd = win.GetParent(hwnd)
  214. }
  215. return hwnd
  216. }
  217. func topWindow(hwnd win.HWND, windowWidth int32, windowHeight int32) (int32, int32, int32, int32) {
  218. //hwnd = GetHwnd(hwnd)
  219. // 获取屏幕的宽度和高度
  220. screenWidth := int32(win.GetSystemMetrics(win.SM_CXSCREEN))
  221. screenHeight := int32(win.GetSystemMetrics(win.SM_CYSCREEN))
  222. // 计算窗口的位置和大小
  223. //windowWidth := int32(1539) // 设置窗口宽度
  224. //windowHeight := int32(1000) // 设置窗口高度
  225. windowX := int32(screenWidth-windowWidth) / 2 // 计算窗口的左上角X坐标
  226. windowY := int32(screenHeight-windowHeight) / 2 // 计算窗口的左上角Y坐标
  227. win.SetForegroundWindow(hwnd) // 将窗口置于前台
  228. ////////
  229. win.SetWindowPos(hwnd, win.HWND_TOPMOST, 0, 0, 0, 0, win.SWP_NOMOVE|win.SWP_NOSIZE)
  230. time.Sleep(500 * time.Millisecond)
  231. win.MoveWindow(hwnd, windowX, windowY, windowWidth, windowHeight, true) // 移动窗口位置和大小
  232. // 设置窗口位置和大小
  233. ////////////////////////////////////////////////////////////////
  234. // win.AnimateWindow(hwnd, 1, win.AW_HIDE|win.AW_CENTER)
  235. // //win.AnimateWindow(hwnd, 1, win.AW_CENTER)
  236. //
  237. // time.Sleep(500 * time.Millisecond)
  238. //
  239. // win.SetWindowPos(hwnd, win.HWND_TOPMOST, windowX, windowY, windowWidth, windowHeight, win.SWP_NOMOVE|win.SWP_NOSIZE)
  240. //
  241. // win.MoveWindow(hwnd, windowX, windowY, windowWidth, windowHeight, true)
  242. //
  243. //win.SetForegroundWindow(hwnd) // 将窗口置于前台
  244. //
  245. // win.AnimateWindow(hwnd, 700, win.AW_BLEND) // 渐显
  246. //
  247. // time.Sleep(500 * time.Millisecond)
  248. return windowX, windowY, windowWidth, windowHeight
  249. }
  250. func start() {
  251. fmt.Printf("搜索企业微信窗口句柄...\n")
  252. hwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))
  253. if hwnd == 0 {
  254. // 未找到窗口
  255. fmt.Printf("搜索失败,未找到窗口句柄。\n")
  256. return
  257. }
  258. //窗口置顶 并居中固定大小
  259. windowsX, windowsY, _, _ := topWindow(hwnd, 1539, 1000)
  260. // 点击通讯录 40-654
  261. mouseMove(int(windowsX+position.AddressX), int(windowsY+position.AddressY))
  262. ////点击添加新联系人
  263. mouseMove(int(windowsX+position.NewContactX), int(windowsY+position.NewContactY))
  264. //点击添加联系人 弹出搜索框
  265. mouseMove(int(windowsX+position.SearchX), int(windowsY+position.SearchY))
  266. time.Sleep(1 * time.Second)
  267. searchHwnd := win.FindWindow(syscall.StringToUTF16Ptr("SearchExternalsWnd"), nil)
  268. if searchHwnd == 0 {
  269. // 未找到窗口
  270. fmt.Printf("搜索外部联系人添加窗口句柄失败。\n")
  271. restore(windowsX, windowsY)
  272. return
  273. }
  274. fmt.Printf("搜索外部联系人添加窗口句柄成功...\n")
  275. fmt.Printf("输入字符...\n")
  276. //win.SetForegroundWindow(searchHwnd) // 将窗口置于前台
  277. time.Sleep(500 * time.Millisecond)
  278. searchX, searchY, _, _ := topWindow(searchHwnd, 600, 462)
  279. win.SetFocus(searchHwnd)
  280. //time.Sleep(500 * time.Millisecond)
  281. //mouseMove(int(windowsX+position.InputX), int(windowsY+position.InputY))
  282. fmt.Printf("点击输入框...\n")
  283. // 循环发送每个字符的键盘消息
  284. for _, c := range phone {
  285. win.SendMessage(searchHwnd, win.WM_CHAR, uintptr(c), 0)
  286. }
  287. // 发送Enter键消息
  288. win.SendMessage(searchHwnd, win.WM_KEYDOWN, uintptr(win.VK_RETURN), 0)
  289. win.SendMessage(searchHwnd, win.WM_KEYUP, uintptr(win.VK_RETURN), 0)
  290. time.Sleep(1000 * time.Millisecond)
  291. win.SetForegroundWindow(searchHwnd) // 将窗口置于前台
  292. time.Sleep(500 * time.Millisecond)
  293. //点击添加
  294. mouseMove(int(searchX+position.AddX), int(searchY+position.AddY))
  295. time.Sleep(500 * time.Millisecond)
  296. //添加确认框句柄搜索
  297. inputReasonWndHwnd := win.FindWindow(syscall.StringToUTF16Ptr("InputReasonWnd"), nil)
  298. if inputReasonWndHwnd == 0 {
  299. // 未找到窗口
  300. fmt.Printf("搜索添加确认框句柄搜索失败。\n")
  301. restore(windowsX, windowsY)
  302. return
  303. }
  304. //inputReasonX, inputReasonY, _, _ := topWindow(inputReasonWndHwnd, 589, 366)
  305. time.Sleep(500 * time.Millisecond)
  306. mouseMove(int(searchX+position.ConfirmX), int(searchY+position.ConfirmY))
  307. restore(windowsX, windowsY)
  308. }
  309. // 恢复
  310. func restore(windowsX, windowsY int32) {
  311. notifyHwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkMessageBoxFrame"), nil)
  312. if notifyHwnd != 0 {
  313. win.PostMessage(notifyHwnd, win.WM_CLOSE, 0, 0)
  314. }
  315. searchHwnd := win.FindWindow(syscall.StringToUTF16Ptr("SearchExternalsWnd"), nil)
  316. if searchHwnd != 0 {
  317. // 发送关闭消息
  318. win.SendMessage(searchHwnd, win.WM_CLOSE, 0, 0)
  319. }
  320. time.Sleep(500 * time.Millisecond)
  321. ////重新点回消息列表
  322. //mouseMove(int(windowsX+position.MessageX), int(windowsY+position.MessageY))
  323. //
  324. //time.Sleep(500 * time.Millisecond)
  325. hwnd := win.FindWindow(syscall.StringToUTF16Ptr("WeWorkWindow"), syscall.StringToUTF16Ptr("企业微信"))
  326. if hwnd == 0 {
  327. // 未找到窗口
  328. fmt.Printf("搜索失败,未找到窗口句柄。\n")
  329. return
  330. }
  331. win.SendMessage(hwnd, win.WM_CLOSE, 0, 0)
  332. //
  333. win.ShowWindow(hwnd, win.SW_RESTORE) // 恢复窗口大小
  334. //win.ShowWindow(hwnd, win.SW_MINIMIZE)
  335. //检查是否有提示框,如果有关闭它
  336. }
  337. type version struct {
  338. Version string `json:"version"`
  339. DownloadUrl string `json:"downloadUrl"`
  340. }
  341. func compareVersion(version1 string, version2 string) int {
  342. var res int
  343. // 通过字符串切割会生成一个数组
  344. ver1Strs := strings.Split(version1, ".") //[1 2 3]
  345. ver2Strs := strings.Split(version2, ".") //[2 3 4 5]
  346. ver1Len := len(ver1Strs)
  347. ver2Len := len(ver2Strs)
  348. //fmt.Println(ver1Strs, ver2Strs)
  349. verLen := ver1Len
  350. if len(ver1Strs) < len(ver2Strs) {
  351. verLen = ver2Len
  352. }
  353. for i := 0; i < verLen; i++ {
  354. var ver1Int, ver2Int int
  355. if i < ver1Len {
  356. // 字符串转换成整数strconv.Atoi
  357. ver1Int, _ = strconv.Atoi(ver1Strs[i])
  358. }
  359. if i < ver2Len {
  360. ver2Int, _ = strconv.Atoi(ver2Strs[i])
  361. }
  362. if ver1Int < ver2Int {
  363. res = -1
  364. break
  365. }
  366. if ver1Int > ver2Int {
  367. res = 1
  368. break
  369. }
  370. }
  371. return res
  372. }
  373. func versionUpgrade(phone string) {
  374. url := "https://xxxx/version.json"
  375. fmt.Printf("当前版本:%s\n", VERSION)
  376. resp, err := http.Get(url)
  377. if err != nil {
  378. fmt.Printf("获取版本失败:%s\n", err)
  379. }
  380. defer resp.Body.Close()
  381. body, err := io.ReadAll(resp.Body)
  382. if err != nil {
  383. fmt.Printf("读取版本失败:%s\n", err)
  384. }
  385. var data version
  386. err = json.Unmarshal(body, &data)
  387. if err != nil {
  388. fmt.Printf("解析版本失败:%s\n", err)
  389. return
  390. }
  391. fmt.Printf("当前版本:%s,远程版本:%s\n", VERSION, data.Version)
  392. //检查本地版本是否小于远程版本
  393. if compareVersion(VERSION, data.Version) >= 0 {
  394. return
  395. }
  396. fmt.Printf("需要更新\n")
  397. //更新
  398. file, err := http.Get(data.DownloadUrl)
  399. if err != nil {
  400. fmt.Printf("访问下载连接失败:%s\n", err)
  401. return
  402. }
  403. defer file.Body.Close()
  404. fileBytes, err := io.ReadAll(file.Body)
  405. if err != nil {
  406. fmt.Printf("更新失败:%s\n", err)
  407. return
  408. }
  409. path, _ := os.Executable()
  410. path = filepath.Dir(path)
  411. dir := filepath.Join(path, "version")
  412. fmt.Println(dir)
  413. _ = os.Mkdir(dir, 0644)
  414. err = os.WriteFile(filepath.Join(dir, "tmp.exe"), fileBytes, 0644)
  415. if err != nil {
  416. fmt.Printf("保存失败:%s\n", err)
  417. return
  418. }
  419. fmt.Printf("保存成功\n")
  420. var cmd *exec.Cmd
  421. upgradePath := filepath.Join(path, "Upgrade.exe")
  422. if phone != "" {
  423. cmd = exec.Command("cmd.exe", "/C", "start", "/B", upgradePath, phone)
  424. } else {
  425. cmd = exec.Command("cmd.exe", "/C", "start", "/B", upgradePath)
  426. }
  427. if err = cmd.Start(); err != nil {
  428. fmt.Println(err)
  429. }
  430. fmt.Println("拉起更新程序")
  431. os.Exit(0)
  432. }

position.go

  1. package main
  2. import (
  3. "errors"
  4. )
  5. func getPosition(dpi float64) (POSITION, error) {
  6. if dpi == 1.0 {
  7. return POSITION{33, 445, 150, 80, 1473, 40, 33, 92, 449, 212, 300, 270}, nil
  8. } else if dpi == 1.5 {
  9. return POSITION{40, 654, 240, 120, 1470, 60, 50, 130, 520, 210, 300, 311}, nil
  10. }
  11. //抛出错误
  12. return POSITION{}, errors.New("dpi must be 1.0 or 1.5")
  13. }

windows.go

  1. package main
  2. import (
  3. "github.com/lxn/win"
  4. "syscall"
  5. "unsafe"
  6. )
  7. var (
  8. user32 = syscall.NewLazyDLL("user32.dll")
  9. shcore = syscall.NewLazyDLL("shcore.dll")
  10. getDpiForWindow = user32.NewProc("GetDpiForWindow")
  11. getProcessDpiAwareness = shcore.NewProc("GetProcessDpiAwareness")
  12. setProcessDpiAwareness = shcore.NewProc("SetProcessDpiAwareness")
  13. processPerMonitorDpiAwareness = 2
  14. procBlockInput = user32.NewProc("BlockInput")
  15. )
  16. func getDpiAwareness() (int, error) {
  17. var awareness uintptr
  18. r, _, err := getProcessDpiAwareness.Call(uintptr(0), uintptr(unsafe.Pointer(&awareness)))
  19. if r != 0 {
  20. return 0, err
  21. }
  22. return int(awareness), nil
  23. }
  24. func setDpiAwareness() error {
  25. r, _, err := setProcessDpiAwareness.Call(uintptr(processPerMonitorDpiAwareness))
  26. if r != 0 {
  27. return err
  28. }
  29. return nil
  30. }
  31. func getScalingFactor(hwnd win.HWND) (float64, error) {
  32. awareness, err := getDpiAwareness()
  33. if err != nil {
  34. return 0, err
  35. }
  36. if awareness == processPerMonitorDpiAwareness {
  37. hdc := win.GetDC(hwnd)
  38. defer win.ReleaseDC(hwnd, hdc)
  39. dpi := win.GetDeviceCaps(hdc, win.LOGPIXELSX)
  40. return float64(dpi) / 96.0, nil
  41. }
  42. hwndDpi, _, _ := getDpiForWindow.Call(uintptr(hwnd))
  43. return float64(hwndDpi) / 96.0, nil
  44. }

上面程序打包得到 ShangHao.exe

与之配套的,还有一个Upgrade程序

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "os/exec"
  6. "path/filepath"
  7. "time"
  8. )
  9. func main() {
  10. fmt.Println("upgrade....")
  11. fmt.Println("rename:")
  12. path, err := os.Executable()
  13. if err != nil {
  14. return
  15. }
  16. path = filepath.Dir(path)
  17. dir := filepath.Join(path, "version")
  18. err = os.Rename(filepath.Join(dir, "tmp.exe"), filepath.Join(path, "ShangHao.exe"))
  19. if err != nil {
  20. return
  21. }
  22. fmt.Println(dir, path)
  23. if err != nil {
  24. return
  25. }
  26. fmt.Println("done")
  27. time.Sleep(1 * time.Second)
  28. args := os.Args
  29. if len(args) > 1 {
  30. phone := args[1]
  31. var cmd *exec.Cmd
  32. shanghao := filepath.Join(path, "ShangHao.exe")
  33. if phone != "" {
  34. cmd = exec.Command("cmd.exe", "/C", "start", shanghao, phone)
  35. } else {
  36. cmd = exec.Command("cmd.exe", "/C", "start", shanghao)
  37. }
  38. err := cmd.Start()
  39. if err != nil {
  40. return
  41. }
  42. } else {
  43. os.Exit(0)
  44. }
  45. //重新吊起源程序
  46. }

打包得到Upgrade.exe

将两个文件放到一起,放到任意目录下,并创建一个批处理文件,管理员权限执行批处理文件

  1. @echo off
  2. setlocal
  3. set "script_dir=%~dp0"
  4. reg delete HKEY_CLASSES_ROOT\ShangHao /f
  5. echo %errorlevel%
  6. reg add HKEY_CLASSES_ROOT\ShangHao /ve /d "URL:ShangHao Protocol Handler" /f
  7. echo %errorlevel%
  8. reg add HKEY_CLASSES_ROOT\ShangHao /v "URL Protocol" /t REG_SZ /d "" /f
  9. echo %errorlevel%
  10. reg add HKEY_CLASSES_ROOT\ShangHao\DefaultIcon /ve /d "%script_dir%ShangHao.exe" /f
  11. echo %errorlevel%
  12. reg add HKEY_CLASSES_ROOT\ShangHao\shell /ve /f
  13. echo %errorlevel%
  14. reg add HKEY_CLASSES_ROOT\ShangHao\shell\open /ve /f
  15. echo %errorlevel%
  16. reg add HKEY_CLASSES_ROOT\ShangHao\shell\open\command /ve /d "\"%script_dir%ShangHao.exe\" \"%%1\"" /f
  17. echo %errorlevel%
  18. endlocal

脚本执行完成后 就可以在浏览器直接调用exe并传参了
比如 企业微信添加1333333333这个手机号对应的微信

  1. <a href="ShangHao://13333333333">点击添加微信</a>