first commit

This commit is contained in:
hero920103 committed 2026-10-24 13:02:19 +08:00
commit 761b95080f
404 files changed
+312608

No files matched your search

+280
View File
@@ -0,0 +1,280 @@
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"
};
}
}