Скрипты EyeAuras — это обычный C#-код, который выполняется внутри runtime-среды EyeAuras. Они одинаково хорошо подходят и для маленьких утилит на 20 строк, и для более крупных решений, которые потом могут вырасти в pack или mini-app.
Если вам нужно исследовать API с помощью AI, начните с AI Discovery Maps. Они разводят широкие вопросы по более узким картам и отделяют базовые концепции SDK от дополнительных пакетов, таких как ImGui SDK и Frida SDK.
Это один из самых важных моментов во всей системе.
Код внутри Script.csx проходит специальную обработку EyeAuras:
Log, Sleep(...), GetService<T>(), AuraTree, Variables, cancellationTokenНапример, прямо внутри Script.csx можно писать так:
Log.Info("Script started");
Sleep(100);
var input = GetService<ISendInputScriptingApi>();
input.KeyPress(Key.F);
Но вспомогательные классы рядом со скриптом — это уже обычный C#. У них нет этого “магического” прямого доступа к песочнице.
Если вы пишете отдельный класс, он не может сам по себе вызывать Log или AuraTree:
public sealed class Helper
{
private readonly IFluentLog log;
public Helper(IFluentLog log)
{
this.log = log;
}
public void DoWork()
{
log.Info("Helper is working");
}
}
Правило простое:
Script.csx — это специальная точка входа EyeAurasScript.csx или в другой runtime-точке композиции и передайте через конструктор или свойстваЭто важно и для людей, и для AI: не у всех частей проекта одинаковые “магические” возможности.
Короткий ответ: скрипты полезны не потому, что “писать софт сложно”, а потому что EyeAuras уже является готовой платформой автоматизации.
Если собирать отдельную программу с нуля, вокруг своей логики вам придется самостоятельно поднимать много инфраструктуры:
В EyeAuras большая часть этой инфраструктуры уже есть. Значит, вы пишете только свою логику и сразу можете использовать готовые ауры, триггеры, переменные, CV API, ввод и UI.
На практике это дает несколько плюсов:
.sln, затем в pack, а потом и в mini-appЕсли ваш проект по сути не использует возможности EyeAuras и не зависит от его runtime-среды, то обычное standalone-приложение на .NET может быть более прямым выбором.
С точки зрения языка это обычный C# на .NET и Roslyn. С точки зрения продукта — это код, который работает внутри песочницы и получает доступ к API EyeAuras: логам, переменным, аурам, вводу, CV, UI и другим сервисам.
Подробнее: Быстрый старт, Песочница
Примеры:
Прежде чем делать отдельную программу, полезно понять, что EyeAuras уже умеет сама:
.slnpack и упаковку для распространенияmini-app, чтобы строить свое приложение поверх EyeAurasЕсли вы делаете приватное или коммерческое решение, лицензирование, key login, pack-и и защита кода могут сэкономить очень много времени.
[Keybind] позволяет привязать обработчик hotkey напрямую к методу скрипта.
Если вы раньше работали с AutoHotkey, идея очень похожа:
F1:: в AHK похож на [Keybind("F1")]*F1:: похож на [Keybind("F1", IgnoreModifiers = true)]~F1:: похож на [Keybind("F1", SuppressKey = false)]d up:: похож на [Keybind("D", ActivationType = KeybindActivationType.KeyUp)]Минимальные примеры:
[Keybind("F1")]
public void OnF1()
{
Log.Info("F1 pressed");
}
[Keybind("F1", IgnoreModifiers = true)]
public void OnF1WithAnyModifiers()
{
Log.Info("Works for F1, Shift+F1, Ctrl+F1");
}
[Keybind("D", ActivationType = KeybindActivationType.KeyUp)]
public void OnDReleased()
{
Log.Info("D released");
}
Чем это удобно на практике:
Две важные детали:
Если нужно не запускать второй обработчик, пока не закончился первый, можно сделать так:
System.Threading.SemaphoreSlim hotkeyGate = new(1, 1);
[Keybind("F2")]
public void RunSingleHotkey()
{
if (!hotkeyGate.Wait(0))
{
Log.Info("Hotkey is already being processed");
return;
}
try
{
Log.Info("Handling F2");
Sleep(300);
}
finally
{
hotkeyGate.Release();
}
}
Небольшой полезный бонус: обработчик keybind также может принимать зависимости через параметры метода, если они уже доступны через DI.
Если hotkey должна быть видна в дереве аур, сочетаться с условиями и жить как часть общей визуальной логики, то вместо [Keybind] часто удобнее использовать триггер HotkeyIsActive.
Подробнее: Hotkeys
Примеры:
Для UI в EyeAuras обычно есть два основных пути: Blazor Windows и ImGui.
ImGui — обычно самый простой вариант для старта.
Script.csxSleep(...) или тяжелый кодAddRenderer(...), а внутри render-метода использовать только уже готовое состояниеЭто хороший выбор, если вы хотите быстро собрать рабочий UI без лишних файлов.
Blazor Windows обычно требует чуть больше структуры, но дает намного более богатый UI.
Script.csx, *.razor, *.cs*.razor, а код в *.cs, потому что так проще читать, и IntelliSense там сейчас лучшеScript.csx обычно только создает окно или overlay, а состояние потом живет уже внутри самого компонентаScript.csx здесь скорее точка входа для создания окна, а не место, которое “рендерит UI каждый кадр”OnInitializedAsync, а JavaScript, которому нужен уже существующий DOM, обычно загружается в OnAfterFirstRenderAsyncНа практике это значит, что Blazor обычно требует чуть больше структуры, зато результат гораздо больше похож на законченный продукт.
Коротко:
ImGui проще, быстрее и ближе к формату “все в одном файле”Blazor Windows сложнее, но красивее и гибчеЕще одно важное различие:
Script.csx обычно выступает как точка входа, а реальный UI живет в Razor-компонентахAddRenderer(...)Минимальный пример lifecycle для Blazor:
[Inject] public PoeShared.Blazor.Services.IJsPoeBlazorUtils PoeBlazorUtils { get; init; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await PoeBlazorUtils.LoadCss("Styles.css");
}
protected override async Task OnAfterFirstRenderAsync()
{
await base.OnAfterFirstRenderAsync();
await PoeBlazorUtils.LoadScript("Script.js");
}
Подробнее: Blazor Windows, Начало работы с ImGui
Примеры:
Есть несколько мелких синтаксических деталей, которые часто удивляют, потому что Script.csx компилируется не так, как обычный .cs-файл. Он компилируется как Roslyn submission script.
Обычные namespace-директивы работают как обычно:
using System.Text;
using OpenQA.Selenium;
Но конструкции для освобождения ресурсов на верхнем уровне Script.csx требуют немного больше внимания.
Если вы хотите освобождать ресурсы прямо на верхнем уровне Script.csx, лучше создать явный scope через {}.
Это относится и к using var, и к using (...) { ... }.
Самый простой вариант:
{
using var canvas = GetService<IOnScreenCanvasScriptingApi>().Create();
Log.Info("Canvas created");
}
Или так:
{
using (var stream = GetService<IScriptFileProvider>().GetFileInfo("docs/help.md").CreateReadStream())
{
Log.Info("Stream opened");
}
}
Почему так:
Script.csx компилируется особым образомВнутри обычных методов, local function, try, if, while и других блоков using обычно работает без проблем.
Практическое правило:
using Some.Namespace; в начале файла пишите как обычноusing var resource = ... на верхнем уровне Script.csx помещайте внутрь { ... }using (...) { ... } на верхнем уровне Script.csx тоже помещайте внутрь внешнего { ... }try { ... }, отдельный scope обычно не нужен, потому что он уже естьПримеры:
В Script.csx можно нормально использовать async/await.
Например:
var text = await File.ReadAllTextAsync("input.txt");
Log.Info(text);
Или так:
async Task<int> LoadValue()
{
await Task.Delay(100);
return 42;
}
var value = await LoadValue();
Log.Info(value);
На практике это значит:
await, компилятор сам подстроит сигнатуруawaitTask<T>Поэтому в повседневной работе можно думать очень просто:
await — просто используйте awaitreturnИ еще одна рекомендация: по возможности избегайте async void и предпочитайте async Task, даже несмотря на то, что некоторые внутренние rewriter-ы EyeAuras умеют автоматически подправлять такие случаи.
Это еще один частый источник путаницы.
Внутри EyeAuras Script.csx не всегда ведет себя так, будто при каждом запуске создается заново. Если скрипт не перекомпилировался и его runtime-контекст остался тем же, часть top-level состояния может переживать повторные вызовы.
На практике это значит:
Variables все равно остается лучшим выборомХорошее практическое правило:
VariablesПесочница — это среда, в которой выполняется ваш скрипт. Она:
Log, Sleep(...), GetService<T>()Если коротко, песочница — это host, который вам не нужно писать самостоятельно.
Подробнее: Песочница
Есть три основных варианта:
[Inject] + init — хороший вариант для базовых зависимостей, которые задаются один раз при старте[Dependency] + set — удобно, когда зависимость должна быть свойством, которое при необходимости можно заменитьGetService<T>() — когда сервис нужен прямо здесь и прямо сейчасМинимальные примеры:
[Inject] public IAuraTreeScriptingApi AuraTree { get; init; }
[Dependency] public ISendInputScriptingApi SendInput { get; set; }
var input = GetService<ISendInputScriptingApi>();
Если вы только начинаете, GetService<T>() обычно самый понятный вариант. В больших кодовых базах свойства с [Inject] или [Dependency] часто делают скрипт чище.
Подробнее: Dependency Injection, Script Container Extensions
Примеры:
cancellationToken автоматически доступен в каждом скрипте. EyeAuras передает его в песочницу до старта скрипта.
Очень коротко: CancellationToken в .NET — это стандартный объект, которым коду говорят: “пора остановиться”. Официальная документация: CancellationToken.
На практике это значит:
Sleep(...) завершается раньшеВажно: EyeAuras умеет автоматически внедрять часть логики отмены, но в реальном коде все равно лучше писать явную, читаемую логику с учетом cancellation самостоятельно.
Здесь есть два основных сценария.
Если вы уже создали overlay, окно, renderer, subscription или другой фоновый объект, и после этого скрипту не нужен активный polling, обычно достаточно вот этого:
cancellationToken.WaitHandle.WaitOne();
Это хорошо подходит для:
Главный плюс в том, что вы не крутите пустой цикл и не тратите CPU без причины.
Если у вас есть активный цикл, используйте явную проверку отмены:
while (!cancellationToken.IsCancellationRequested)
{
DoSomething();
Sleep(50);
}
Это хорошо подходит для:
Важные моменты:
Sleep(...), если только у вас нет очень веской причиныSleep(...) в EyeAuras уже умеет завершаться раньше при остановке скриптаКоротко:
WaitOne() для сценария “все уже работает, просто живи до остановки”while (!cancellationToken.IsCancellationRequested) для сценария “пока жив — продолжай работать”Примеры:
В Script.csx почти всегда лучше использовать sandbox-версию Sleep(...).
Почему:
Sleep(...) связан с cancellationToken и может завершиться раньше при остановкеThread.Sleep(...) внутри Script.csx дополнительно переписывается rewriter-ом в Sleep(...)Подробнее: Переработка Sleep()
Ниже — изображение со сравнением точности разных способов ожидания:

Практическое правило:
Script.csx пишите Sleep(...)Thread.Sleep(...)Task.Delay(...) используйте только если вам действительно нужна async-модель и вы понимаете зачемEyeAuras применяет code rewriter-ы к Script.csx. Это еще одна причина, почему важно отделять код скрипта от обычных классов.
На практике достаточно помнить хотя бы следующее:
while (true), добавляя проверку cancellationTokenThread.Sleep(...) в коде скрипта заменяется на Sleep(...)CancellationToken, runtime может автоматически подстроитьЭто полезная страховка, но не повод писать небрежный код. Лучшая практика все равно такая:
while (!cancellationToken.IsCancellationRequested)Sleep(...)В EyeAuras полезно думать не только в терминах “какой объект я создал”, но и “когда он должен умереть”.
Если объект потом нужно корректно очистить, его обычно привязывают к CompositeDisposable. Официальная справка: CompositeDisposable.
Важные уровни lifecycle такие:
Anchors — это якоря песочницы или объекта. Если добавить туда ресурс, он будет жить столько, сколько живет сама песочница или объектExecutionAnchors — это якоря текущего запуска скрипта. При остановке и повторном старте скрипта они создаются зановоCompositeDisposable удобен внутри ваших helper-классов, когда вы хотите собрать несколько ресурсов в один cleanup-“пакет”Короткое правило:
ExecutionAnchorsAnchorsПример:
var overlay = GetService<IImGuiExperimentalApi>()
.AddTo(ExecutionAnchors);
Это значит, что overlay живет только в рамках текущего запуска скрипта.
Примеры:
IScriptingApiContext — это внутренний объект контекста скрипта. В нем есть:
Solution текущего скриптаAnchors для ресурсов этого контекстаОбычному автору скрипта обычно не нужно создавать IScriptingApiContext вручную. Но общую идею полезно понимать:
AnchorsЭто становится особенно важно, если вы делаете собственные библиотеки, расширения или переносите код из одного файла в более крупную структуру.
Если ваш скрипт создает ресурсы, которые должны умереть вместе с текущим запуском скрипта, привязывайте их к ExecutionAnchors.
Типичные примеры:
Идея простая: если ресурс относится именно к текущему выполнению скрипта, привяжите его к lifetime этого выполнения.
Примеры:
На практике удобно думать о трех уровнях:
Для большинства сценариев лучший API — это ScriptVariable<T> через Variables.Get<T>(...).
var attempts = Variables.Get<int>("attempts");
attempts.Value++;
Плюсы этого подхода:
objectПодробнее: Переменные, IVariablesScriptingApi
Примеры:
Скрипт обычно имеет доступ к текущей ауре и текущей папке:
AuraTree.AuraAuraTree.FolderТакже можно находить другие объекты по пути:
FindAuraByPath(...) / GetAuraByPath(...)FindFolderByPath(...) / GetFolderByPath(...)GetTriggerByPath<T>(...)Полезное правило:
Find*, когда отсутствие объекта — нормальная ситуацияGet*, когда отсутствие объекта — это ошибкаВажно: у аур тоже есть свои переменные. То есть данные можно хранить не только на уровне текущего скрипта, но и на уровне ауры или папки.
var aura = AuraTree.GetAuraByPath(@".\Gameplay\Boss");
var phase = Variables.Get<string>(aura, "phase");
Подробнее: IAuraTreeScriptingApi, IAuraAccessor, Как найти ауру
Примеры:
Некоторые возможности EyeAuras упакованы как отдельные container extensions. Именно так в DI-контейнер подключается новый набор сервисов.
Например, некоторые библиотеки ожидают явной активации прямо из Script.csx:
AddNewExtension<ImGuiContainerExtensions>();
var imgui = GetService<IImGuiExperimentalApi>();
AddNewExtension<FridaContainerExtensions>();
var frida = GetService<IFridaExperimentalApi>();
Это особенно удобно для модульных пакетов, которые не хочется держать включенными всегда.
Подробнее: Script Container Extensions, Начало работы с ImGui
Примеры:
Если библиотека уже существует как NuGet-пакет, это обычно лучший вариант.
Почему:
pack EyeAuras заранее подгружает NuGet-зависимости и включает их в packБазовый синтаксис:
#r "nuget: Some.Package, 1.2.3"
Минимальный пример:
#r "nuget: Newtonsoft.Json, 13.0.3"
using Newtonsoft.Json;
Log.Info(JsonConvert.SerializeObject(new { Hello = "World" }));
Важно: директивы #r должны быть написаны в Script.csx. В обычных связанных *.cs-классах они не работают.
Подробнее: NuGet и сборки, Упаковка NuGet внутрь pack-ов
Локальные сборки полезны, когда:
Обычно самый переносимый вариант такой:
#r "assemblyPath: MyLib.dll"Минимальный пример:
#r "assemblyPath: MyLib.dll"
using MyLib;
var helper = new SomeLibraryHelper();
Log.Info(helper.ToString());
Это намного лучше, чем абсолютный путь вроде D:\\Work\\Something\\MyLib.dll, потому что такой путь работает только на машине автора.
Абсолютные пути лучше оставлять для локальных экспериментов и отладки. Для переносимых решений предпочитайте:
Подробнее: Embedded resources, NuGet и сборки
Pack — это переносимая версия вашего решения на базе EyeAuras. Обычно он включает:
Что важно для скриптов:
Если скрипт должен запускаться где-то еще, кроме вашей собственной машины, хорошее базовое правило такое:
NuGet > embedded DLL > absolute DLL pathПодробнее: Packs, Упаковка NuGet внутрь pack-ов, ресурсы в pack-ах
Mini-app — это уже не просто “скрипт внутри EyeAuras”, а ваш собственный продукт поверх EyeAuras.
Коротко:
Mini-app подходит, если вы хотите:
Да. Это одна из ключевых сильных сторон всей системы.
Типичный путь роста выглядит так:
.csx или C# Action.slnImport / Live Importpackmini-app, если это понадобитсяТо есть скрипт EyeAuras — это не тупиковый “встроенный макрос”. Это обычная точка входа в более крупный проект.
Подробнее: Интеграция с IDE, Mini-app
Если вы строите свой продукт поверх EyeAuras, тут есть несколько связанных тем:
pack для распространенияДля обычных локальных скриптов об этом можно вообще не думать. Для коммерческих или приватных решений это уже становится важной частью архитектуры.
Если задачу можно чисто собрать на готовых триггерах, аурах, переменных и actions, обычно с этого и лучше начинать. Скрипты особенно хорошо работают как “умная прослойка” между уже готовыми частями платформы.
Обычно проще и надежнее:
AuraTreeчем писать всю механику вручную с нуля.
Хотя EyeAuras местами умеет помогать автоматически, явный код обычно лучше:
Хорошие базовые шаблоны:
cancellationToken.WaitHandle.WaitOne();while (!cancellationToken.IsCancellationRequested) { ... }Правило простое, но очень полезное.
В Script.csx совершенно нормально писать:
Log.Info(...)Sleep(...)GetService<T>()AuraTree.Get...(...)А в helper-классах лучше либо:
GetService<T>(), чтобы контейнер собрал зависимости за васЕсли решение потом будут запускать другие люди, избегайте:
D:\\...Для переносимых решений почти всегда лучше использовать:
#r "nuget: ..."В старых примерах и документации вам еще может встретиться ISendInputUnstableScriptingApi, но для нового кода лучше использовать ISendInputScriptingApi.
Старый API полезно знать в основном потому, что он все еще может попадаться в legacy-скриптах.
Unstable в названии API обычно не значит “сломанный” или “опасный”. Обычно это лишь значит, что интерфейс еще может меняться между версиями. Если уже есть стабильный аналог — выбирайте его. Если нужная вам возможность пока есть только в Unstable, использовать ее нормально — просто учитывайте возможные breaking changes при обновлении.
Примеры с новым API:
Можно работать и через строковый индексатор, но типизированный доступ через ScriptVariable<T> обычно заметно чище и безопаснее.
Это простое правило сильно снижает количество случайных падений и делает намерение кода заметно понятнее.
Это особенно важно для:
Так stop/start ведет себя предсказуемо, и скрипт оставляет после себя меньше мусора.
Примеры:
Если пакет явно говорит вызвать AddNewExtension<T>(), это не просто using. Это реальная регистрация нового набора сервисов в контейнере.
Типичный пример:
AddNewExtension<ImGuiContainerExtensions>();
var imgui = GetService<IImGuiExperimentalApi>();
Без регистрации сервис может просто не появиться в контейнере.
Примеры:
Если код уже стал большим, не пытайтесь бесконечно держать все в одном файле.
Хороший момент для перехода — когда:
Именно для этого и нужны Export / Import / Live Import.
Для script-проектов, которые уже больше похожи на приложение — с bot loop, собственным script runtime, корневым ImGui UI, Blazor tool windows, профилями и буферизованным сохранением конфига — используйте рецепт ImGui memory bot recipe.
И еще одна практическая деталь: Export очищает целевую папку перед экспортом проекта, поэтому не экспортируйте в директорию, где лежат важные файлы.
Если вы регулярно пишете или поддерживаете большие скрипты, очень рекомендуется работать в связке вроде IDE + AI + EyeAuras MCP. Мой текущий выбор — Rider + AI Assistant/Codex + EyeAuras MCP, но Visual Studio и VS Code тоже вполне подходят.
Подробнее: Интеграция IDE, AI и MCP
Для большинства задач самый простой и надежный вариант — использовать настроенный trigger Image Search и читать его результат из скрипта.
var trigger = AuraTree.GetTriggerByPath<IImageSearchTrigger>(@".\ImageSearch");
var result = trigger.Refresh();
if (!result.Detected.Success)
{
Log.Warn("Image not found");
return;
}
var screenRect = result.ToScreen(result.Detected.Bounds.Value);
Log.Info($"Image found @ {screenRect}");
Подробнее: Как найти изображение
См. также:
Для этого обычно нужен ISendInputScriptingApi:
var input = GetService<ISendInputScriptingApi>();
input.KeyPress(Key.F);
input.MouseLeftClick();
В зависимости от задачи можно использовать:
KeyPress(...)KeyDown(...) / KeyUp(...)MouseMoveTo(...)MouseLeftClick() / MouseRightClick()Подробнее: ISendInputScriptingApi
Примеры:
Общий шаблон почти всегда один и тот же:
var input = GetService<ISendInputScriptingApi>();
var trigger = AuraTree.GetTriggerByPath<IImageSearchTrigger>(@".\ImageSearch");
var result = trigger.Refresh();
if (!result.Detected.Success)
{
return;
}
var screenRect = result.ToScreen(result.Detected.Bounds.Value);
input.MouseMoveTo(screenRect.Center());
input.MouseLeftClick();
Для текста логика такая же, просто источником данных будет Text Search.
Подробнее: Как кликнуть по распознанному тексту
См. также:
Это один из самых частых источников ошибок.
В EyeAuras обычно приходится иметь дело с тремя типами координат:
Безопасное правило простое:
ToScreen(...) или ToScreenPoint(...)Самые полезные методы:
ToScreen(rect)ToScreenPoint(rect)Center()Подробнее: WindowImageProcessedEventArgs
Примеры:
Embedded resources удобны, когда нужно поставлять файлы вместе со скриптом:
Два самых простых сценария:
Показать изображение в Blazor:
<img src="Images/logo.png" />
Прочитать текстовый файл из скрипта:
var files = GetService<IScriptFileProvider>();
var helpText = files.ReadAllText("docs/help.md");
Log.Info(helpText);
Небольшая практическая деталь, которая часто экономит время: лучше писать Images/logo.png или docs/help.md, а не просто logo.png или help.md. Короткие имена удобны, но если окончания путей совпадут, легко случайно взять не тот файл.
Если библиотека уже есть в NuGet, почти всегда лучше подключать ее через #r "nuget: ...". Embedded resources особенно хороши для ваших собственных файлов и переносимых DLL.
Подробнее: Embedded resources
Примеры: