wechat_ai/agent/executor.go

179 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"encoding/json"
"fmt"
"math"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
func maybeScrollBeforeObserve(config Config, summaries []RegionSummary, screenshotPath string) error {
if config.Agent.ObserveMode != "scroll_first" {
return nil
}
if runtime.GOOS != "darwin" {
return fmt.Errorf("observe_mode=scroll_first currently supports macOS only")
}
chat, ok := regionByType(summaries)["chat_content"]
if !ok {
return fmt.Errorf("cannot scroll before observe: chat_content region not found")
}
maxScrolls := config.Agent.MaxScrolls
if maxScrolls <= 0 {
maxScrolls = 1
}
for index := 0; index < maxScrolls; index++ {
window, err := findWeChatWindow()
if err != nil {
return err
}
pointX := window.X + chat.CenterSource[0]
pointY := window.Y + chat.CenterSource[1]
logTask(fmt.Sprintf("观察前滚动聊天区域round=%d point=[%.1f, %.1f] delta_y=%d window=[%.0f, %.0f, %.0f, %.0f]", index+1, pointX, pointY, config.Agent.ScrollDeltaY, window.X, window.Y, window.Width, window.Height))
if err := scrollAt(pointX, pointY, config.Agent.ScrollDeltaY); err != nil {
return err
}
time.Sleep(700 * time.Millisecond)
if err := captureWindow(window, screenshotPath); err != nil {
return err
}
logInfo("已滚动并覆盖更新截图 WeChat.jpg")
}
return nil
}
func findWeChatWindow() (WindowBounds, error) {
script := `
import CoreGraphics
import Foundation
let options = CGWindowListOption(arrayLiteral: [.optionOnScreenOnly, .excludeDesktopElements])
guard let infos = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else {
exit(1)
}
for info in infos {
let owner = info[kCGWindowOwnerName as String] as? String ?? ""
let title = info[kCGWindowName as String] as? String ?? ""
let layer = info[kCGWindowLayer as String] as? Int ?? -1
if layer == 0 && (owner.contains("微信") || owner.lowercased().contains("wechat") || title == "微信") {
if let bounds = info[kCGWindowBounds as String] as? [String: Any],
let x = bounds["X"] as? Double,
let y = bounds["Y"] as? Double,
let w = bounds["Width"] as? Double,
let h = bounds["Height"] as? Double {
let id = info[kCGWindowNumber as String] as? Int ?? 0
let result: [String: Any] = ["x": x, "y": y, "width": w, "height": h, "owner": owner, "title": title, "id": id]
let data = try! JSONSerialization.data(withJSONObject: result)
print(String(data: data, encoding: .utf8)!)
exit(0)
}
}
}
exit(2)
`
output, err := runSwift(script)
if err != nil {
return WindowBounds{}, fmt.Errorf("find WeChat window failed: %w", err)
}
var raw struct {
X float64 `json:"x"`
Y float64 `json:"y"`
Width float64 `json:"width"`
Height float64 `json:"height"`
Owner string `json:"owner"`
Title string `json:"title"`
ID int `json:"id"`
}
if err := json.Unmarshal([]byte(output), &raw); err != nil {
return WindowBounds{}, err
}
return WindowBounds(raw), nil
}
func scrollAt(x float64, y float64, deltaY int) error {
if deltaY == 0 {
deltaY = 6
}
script := fmt.Sprintf(`
import CoreGraphics
import Foundation
import AppKit
NSWorkspace.shared.runningApplications
.first { $0.localizedName == "微信" || $0.localizedName == "WeChat" }?
.activate(options: [.activateIgnoringOtherApps])
usleep(300000)
let point = CGPoint(x: %.3f, y: %.3f)
let move = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: point, mouseButton: .left)
move?.post(tap: .cghidEventTap)
usleep(80000)
let down = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: point, mouseButton: .left)
let up = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: point, mouseButton: .left)
down?.post(tap: .cghidEventTap)
usleep(60000)
up?.post(tap: .cghidEventTap)
usleep(120000)
let delta = Int32(%d)
for _ in 0..<8 {
let lineScroll = CGEvent(scrollWheelEvent2Source: nil, units: .line, wheelCount: 1, wheel1: delta, wheel2: 0, wheel3: 0)
lineScroll?.post(tap: .cghidEventTap)
usleep(45000)
}
for _ in 0..<4 {
let pixelScroll = CGEvent(scrollWheelEvent2Source: nil, units: .pixel, wheelCount: 1, wheel1: delta * 80, wheel2: 0, wheel3: 0)
pixelScroll?.post(tap: .cghidEventTap)
usleep(45000)
}
`, x, y, deltaY)
_, err := runSwift(script)
return err
}
func captureWindow(window WindowBounds, screenshotPath string) error {
if err := os.MkdirAll(filepath.Dir(screenshotPath), 0755); err != nil {
return err
}
rect := fmt.Sprintf("%d,%d,%d,%d", int(math.Round(window.X)), int(math.Round(window.Y)), int(math.Round(window.Width)), int(math.Round(window.Height)))
command := exec.Command("screencapture", "-x", "-R", rect, screenshotPath)
if output, err := command.CombinedOutput(); err != nil {
return fmt.Errorf("screencapture failed: %w output=%s", err, string(output))
}
return nil
}
func runSwift(script string) (string, error) {
command := exec.Command("swift", "-")
command.Stdin = strings.NewReader(script)
var stderr bytes.Buffer
command.Stderr = &stderr
output, err := command.Output()
if err != nil {
return "", fmt.Errorf("%w stderr=%s", err, stderr.String())
}
return strings.TrimSpace(string(output)), nil
}