Files
anjianjingling/Lua/ScriptApi.cs
T
2026-10-24 13:02:19 +08:00

281 lines
13 KiB
C#
Raw 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.
using MoonSharp.Interpreter;
namespace anjianjingling.Lua
{
/// <summary>
/// 把自动化能力注册进 Lua 全局环境。
/// 两套写法并存:
/// 1) 全局简写(按键精灵风格) moveTo(100,200) leftClick() delay(500)
/// 2) 命名空间写法 mouse.moveTo(100,200) mouse.click() key.press("ENTER")
/// </summary>
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<string>)(m => host.Log(m));
g["delay"] = (Action<double>)(ms => engine.Sleep(ms));
g["sleep"] = (Action<double>)(ms => engine.Sleep(ms));
g["delayRandom"] = (Action<double, double>)((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<double, double, double>)((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<double>)(() => DateTimeOffset.Now.ToUnixTimeMilliseconds());
g["stop"] = (Action)(() => throw new AbortSignal(false));
// ===================== 鼠标 =====================
var mouse = new Table(script);
mouse["moveTo"] = (Action<int, int>)((x, y) => { engine.Tick(); host.MouseMove(x, y); });
mouse["moveR"] = (Action<int, int>)((dx, dy) => { engine.Tick(); host.MouseMoveR(dx, dy); });
mouse["down"] = (Action<string>)(b => { engine.Tick(); host.MouseDown(NormBtn(b)); });
mouse["up"] = (Action<string>)(b => { engine.Tick(); host.MouseUp(NormBtn(b)); });
mouse["wheel"] = (Action<int>)(d => { engine.Tick(); host.MouseWheel(d); });
mouse["drag"] = (Action<int, int, int, int, int>)((x1, y1, x2, y2, ms) =>
{
engine.Tick();
host.MouseDrag(x1, y1, x2, y2, ms <= 0 ? 400 : ms);
});
mouse["pos"] = (Func<DynValue>)(() =>
{
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<string>)(k => { engine.Tick(); host.KeyPress(k); });
key["down"] = (Action<string>)(k => { engine.Tick(); host.KeyDown(k); });
key["up"] = (Action<string>)(k => { engine.Tick(); host.KeyUp(k); });
key["hotkey"] = (Action<string>)(k => { engine.Tick(); host.HotKey(k); });
key["type"] = (Action<string>)(t => { engine.Tick(); host.TypeText(t); });
g["key"] = key;
// ===================== 屏幕 / 视觉 =====================
var screen = new Table(script);
screen["size"] = (Func<DynValue>)(() =>
{
engine.Tick();
var (w, h) = host.ScreenSize();
return DynValue.NewTuple(DynValue.NewNumber(w), DynValue.NewNumber(h));
});
screen["color"] = (Func<int, int, string>)((x, y) => { engine.Tick(); return host.ColorAt(x, y); });
screen["shot"] = (Action<string>)(p => { engine.Tick(); host.ScreenShot(p); });
screen["pick"] = (Func<string, string>)(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<string, string>)((p, args) => { engine.Tick(); host.Launch(p, args); });
win["activate"] = (Func<string, bool>)(t => { engine.Tick(); return host.ActivateWindow(t); });
win["list"] = (Func<DynValue>)(() =>
{
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<double>)(() => DateTimeOffset.Now.ToUnixTimeMilliseconds());
sys["sleep"] = (Action<double>)(ms => engine.Sleep(ms));
sys["speed"] = (Func<double, double>)(s => { engine.Speed = s <= 0 ? 1 : s; return engine.Speed; });
g["sys"] = sys;
// ===================== 剪贴板 =====================
var clip = new Table(script);
clip["get"] = (Func<string>)(() => host.ClipGet());
clip["set"] = (Action<string>)(t => host.ClipSet(t));
g["clip"] = clip;
// ===================== 浏览器 =====================
var browser = new Table(script);
browser["open"] = (Action<string>)(u => { engine.Tick(); host.BrowserOpen(u); });
browser["click"] = (Func<string, bool>)(s => { engine.Tick(); return host.BrowserClick(s); });
browser["type"] = (Func<string, string, bool>)((s, t) => { engine.Tick(); return host.BrowserType(s, t); });
browser["wait"] = (Func<string, int, bool>)((s, ms) => { engine.Tick(); return host.BrowserWaitFor(s, ms <= 0 ? 10000 : ms); });
browser["text"] = (Func<string, string>)(s => { engine.Tick(); return host.BrowserGetText(s); });
browser["js"] = (Func<string, string>)(js => { engine.Tick(); return host.BrowserExecJs(js); });
browser["shot"] = (Action<string>)(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"
};
}
}