281 lines
13 KiB
C#
281 lines
13 KiB
C#
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"
|
||
};
|
||
}
|
||
}
|