using MoonSharp.Interpreter; namespace anjianjingling.Lua { /// /// 把自动化能力注册进 Lua 全局环境。 /// 两套写法并存: /// 1) 全局简写(按键精灵风格) moveTo(100,200) leftClick() delay(500) /// 2) 命名空间写法 mouse.moveTo(100,200) mouse.click() key.press("ENTER") /// internal static class ScriptApi { private const double DefaultSim = 0.85; private const int DefaultTolerance = 10; public static void Bind(Script script, LuaEngine engine, ILuaHost host) { var g = script.Globals; // ===================== 基础 ===================== g["log"] = (Action)(m => host.Log(m)); g["delay"] = (Action)(ms => engine.Sleep(ms)); g["sleep"] = (Action)(ms => engine.Sleep(ms)); g["delayRandom"] = (Action)((a, b) => { int lo = (int)Math.Min(a, b), hi = (int)Math.Max(a, b); engine.Sleep(lo >= hi ? lo : Random.Shared.Next(lo, hi + 1)); }); g["random"] = (Func)((a, b) => { int lo = (int)Math.Min(a, b), hi = (int)Math.Max(a, b); return lo >= hi ? lo : Random.Shared.Next(lo, hi + 1); }); g["now"] = (Func)(() => DateTimeOffset.Now.ToUnixTimeMilliseconds()); g["stop"] = (Action)(() => throw new AbortSignal(false)); // ===================== 鼠标 ===================== var mouse = new Table(script); mouse["moveTo"] = (Action)((x, y) => { engine.Tick(); host.MouseMove(x, y); }); mouse["moveR"] = (Action)((dx, dy) => { engine.Tick(); host.MouseMoveR(dx, dy); }); mouse["down"] = (Action)(b => { engine.Tick(); host.MouseDown(NormBtn(b)); }); mouse["up"] = (Action)(b => { engine.Tick(); host.MouseUp(NormBtn(b)); }); mouse["wheel"] = (Action)(d => { engine.Tick(); host.MouseWheel(d); }); mouse["drag"] = (Action)((x1, y1, x2, y2, ms) => { engine.Tick(); host.MouseDrag(x1, y1, x2, y2, ms <= 0 ? 400 : ms); }); mouse["pos"] = (Func)(() => { engine.Tick(); var p = host.MousePosition(); return DynValue.NewTuple(DynValue.NewNumber(p.X), DynValue.NewNumber(p.Y)); }); // click 支持多种参数形态 mouse["click"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); string btn = "left"; int? x = null, y = null; if (a.Count >= 3 && a[0].Type == DataType.Number) { x = (int)Math.Round(a[0].Number); y = (int)Math.Round(a[1].Number); btn = a[2].Type == DataType.String ? a[2].String : "left"; } else if (a.Count >= 2 && a[0].Type == DataType.Number) { x = (int)Math.Round(a[0].Number); y = (int)Math.Round(a[1].Number); } else if (a.Count >= 1 && a[0].Type == DataType.String) { btn = a[0].String; } host.MouseClick(NormBtn(btn), x, y); return DynValue.Nil; }); mouse["doubleClick"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); int? x = a.Count >= 2 && a[0].Type == DataType.Number ? (int)Math.Round(a[0].Number) : null; int? y = a.Count >= 2 && a[0].Type == DataType.Number ? (int)Math.Round(a[1].Number) : null; host.MouseDoubleClick(x, y); return DynValue.Nil; }); g["mouse"] = mouse; // ===================== 键盘 ===================== var key = new Table(script); key["press"] = (Action)(k => { engine.Tick(); host.KeyPress(k); }); key["down"] = (Action)(k => { engine.Tick(); host.KeyDown(k); }); key["up"] = (Action)(k => { engine.Tick(); host.KeyUp(k); }); key["hotkey"] = (Action)(k => { engine.Tick(); host.HotKey(k); }); key["type"] = (Action)(t => { engine.Tick(); host.TypeText(t); }); g["key"] = key; // ===================== 屏幕 / 视觉 ===================== var screen = new Table(script); screen["size"] = (Func)(() => { engine.Tick(); var (w, h) = host.ScreenSize(); return DynValue.NewTuple(DynValue.NewNumber(w), DynValue.NewNumber(h)); }); screen["color"] = (Func)((x, y) => { engine.Tick(); return host.ColorAt(x, y); }); screen["shot"] = (Action)(p => { engine.Tick(); host.ScreenShot(p); }); screen["pick"] = (Func)(p => { engine.Tick(); return host.PickRegion(string.IsNullOrWhiteSpace(p) ? null : p) ?? ""; }); screen["findColor"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); string hex = a.Count > 0 && a[0].Type == DataType.String ? a[0].String : "FF0000"; int tol = a.Count > 1 && a[1].Type == DataType.Number ? (int)Math.Round(a[1].Number) : DefaultTolerance; var (w, h) = host.ScreenSize(); int x1 = a.Count > 2 ? (int)Math.Round(a[2].Number) : 0; int y1 = a.Count > 3 ? (int)Math.Round(a[3].Number) : 0; int x2 = a.Count > 4 ? (int)Math.Round(a[4].Number) : w - 1; int y2 = a.Count > 5 ? (int)Math.Round(a[5].Number) : h - 1; var p = host.FindColor(x1, y1, x2, y2, hex, tol); return DynValue.NewTuple(DynValue.NewNumber(p.X), DynValue.NewNumber(p.Y)); }); screen["findImage"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); string path = a.Count > 0 && a[0].Type == DataType.String ? a[0].String : ""; double sim = a.Count > 1 && a[1].Type == DataType.Number ? a[1].Number : DefaultSim; var (w, h) = host.ScreenSize(); int x1 = a.Count > 2 ? (int)Math.Round(a[2].Number) : 0; int y1 = a.Count > 3 ? (int)Math.Round(a[3].Number) : 0; int x2 = a.Count > 4 ? (int)Math.Round(a[4].Number) : w - 1; int y2 = a.Count > 5 ? (int)Math.Round(a[5].Number) : h - 1; var r = host.FindImage(x1, y1, x2, y2, path, sim); return DynValue.NewTuple( DynValue.NewNumber(r.X), DynValue.NewNumber(r.Y), DynValue.NewNumber(r.Score)); }); screen["waitImage"] = DynValue.NewCallback((ctx, a) => { string path = a.Count > 0 && a[0].Type == DataType.String ? a[0].String : ""; int timeout = a.Count > 1 && a[1].Type == DataType.Number ? (int)Math.Round(a[1].Number) : 10000; double sim = a.Count > 2 && a[2].Type == DataType.Number ? a[2].Number : DefaultSim; var (w, h) = host.ScreenSize(); var sw = System.Diagnostics.Stopwatch.StartNew(); int rx = -1, ry = -1; double best = 0; while (sw.ElapsedMilliseconds < timeout) { engine.Tick(); var r = host.FindImage(0, 0, w - 1, h - 1, path, sim); best = r.Score; if (r.X >= 0) { rx = r.X; ry = r.Y; break; } engine.Sleep(300); } host.Log(rx >= 0 ? $"等待图片出现:找到 ({rx},{ry}) 相似度 {best:0.00}" : $"等待图片超时:{path}(最高 {best:0.00})"); return DynValue.NewTuple( DynValue.NewNumber(rx), DynValue.NewNumber(ry), DynValue.NewNumber(best)); }); screen["exists"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); string path = a.Count > 0 && a[0].Type == DataType.String ? a[0].String : ""; double sim = a.Count > 1 && a[1].Type == DataType.Number ? a[1].Number : DefaultSim; var (w, h) = host.ScreenSize(); var r = host.FindImage(0, 0, w - 1, h - 1, path, sim); return DynValue.NewBoolean(r.X >= 0); }); g["screen"] = screen; // ===================== 窗口 / 系统 ===================== var win = new Table(script); win["launch"] = (Action)((p, args) => { engine.Tick(); host.Launch(p, args); }); win["activate"] = (Func)(t => { engine.Tick(); return host.ActivateWindow(t); }); win["list"] = (Func)(() => { engine.Tick(); var list = new Table(script); var arr = host.ListWindows(); for (int i = 0; i < arr.Length; i++) list[i + 1] = arr[i]; return DynValue.NewTable(list); }); g["win"] = win; var sys = new Table(script); sys["time"] = (Func)(() => DateTimeOffset.Now.ToUnixTimeMilliseconds()); sys["sleep"] = (Action)(ms => engine.Sleep(ms)); sys["speed"] = (Func)(s => { engine.Speed = s <= 0 ? 1 : s; return engine.Speed; }); g["sys"] = sys; // ===================== 剪贴板 ===================== var clip = new Table(script); clip["get"] = (Func)(() => host.ClipGet()); clip["set"] = (Action)(t => host.ClipSet(t)); g["clip"] = clip; // ===================== 浏览器 ===================== var browser = new Table(script); browser["open"] = (Action)(u => { engine.Tick(); host.BrowserOpen(u); }); browser["click"] = (Func)(s => { engine.Tick(); return host.BrowserClick(s); }); browser["type"] = (Func)((s, t) => { engine.Tick(); return host.BrowserType(s, t); }); browser["wait"] = (Func)((s, ms) => { engine.Tick(); return host.BrowserWaitFor(s, ms <= 0 ? 10000 : ms); }); browser["text"] = (Func)(s => { engine.Tick(); return host.BrowserGetText(s); }); browser["js"] = (Func)(js => { engine.Tick(); return host.BrowserExecJs(js); }); browser["shot"] = (Action)(p => { engine.Tick(); host.BrowserShot(p); }); browser["back"] = (Action)(() => { engine.Tick(); host.BrowserBack(); }); browser["forward"] = (Action)(() => { engine.Tick(); host.BrowserForward(); }); browser["reload"] = (Action)(() => { engine.Tick(); host.BrowserReload(); }); g["browser"] = browser; // ===================== 全局简写 ===================== g["moveTo"] = mouse["moveTo"]; g["moveR"] = mouse["moveR"]; g["click"] = mouse["click"]; g["leftClick"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); int? x = a.Count >= 2 ? (int)Math.Round(a[0].Number) : null; int? y = a.Count >= 2 ? (int)Math.Round(a[1].Number) : null; host.MouseClick("left", x, y); return DynValue.Nil; }); g["rightClick"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); int? x = a.Count >= 2 ? (int)Math.Round(a[0].Number) : null; int? y = a.Count >= 2 ? (int)Math.Round(a[1].Number) : null; host.MouseClick("right", x, y); return DynValue.Nil; }); g["middleClick"] = DynValue.NewCallback((ctx, a) => { engine.Tick(); int? x = a.Count >= 2 ? (int)Math.Round(a[0].Number) : null; int? y = a.Count >= 2 ? (int)Math.Round(a[1].Number) : null; host.MouseClick("middle", x, y); return DynValue.Nil; }); g["doubleClick"] = mouse["doubleClick"]; g["wheel"] = mouse["wheel"]; g["drag"] = mouse["drag"]; g["keyPress"] = key["press"]; g["keyDown"] = key["down"]; g["keyUp"] = key["up"]; g["hotkey"] = key["hotkey"]; g["typeText"] = key["type"]; g["sayText"] = key["type"]; g["findImage"] = screen["findImage"]; g["findColor"] = screen["findColor"]; g["waitImage"] = screen["waitImage"]; g["exists"] = screen["exists"]; g["colorAt"] = screen["color"]; g["screenshot"] = screen["shot"]; g["run"] = win["launch"]; g["activate"] = win["activate"]; g["clipGet"] = clip["get"]; g["clipSet"] = clip["set"]; } private static string NormBtn(string b) => b.Trim().ToLowerInvariant() switch { "右" or "right" or "r" or "rightclick" => "right", "中" or "middle" or "m" => "middle", _ => "left" }; } }