Initial commit: POE2 automated trade bot
Monitors pathofexile.com/trade2 for new listings, travels to seller hideouts, buys items from public stash tabs, and stores them. Includes persistent C# OCR daemon for fast screen capture + Windows native OCR, web dashboard for managing trade links and settings, and full game automation via Win32 SendInput. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
41d174195e
28 changed files with 6449 additions and 0 deletions
293
tools/OcrDaemon/Program.cs
Normal file
293
tools/OcrDaemon/Program.cs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Ocr;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
// Make GDI capture DPI-aware so coordinates match physical pixels
|
||||
SetProcessDPIAware();
|
||||
|
||||
// Pre-create the OCR engine (reused across all requests)
|
||||
var ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
|
||||
if (ocrEngine == null)
|
||||
{
|
||||
WriteResponse(new ErrorResponse("Failed to create OCR engine. Ensure a language pack is installed."));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Signal ready
|
||||
WriteResponse(new ReadyResponse());
|
||||
|
||||
// JSON options
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
// Main loop: read one JSON line, handle, write one JSON line
|
||||
var stdin = Console.In;
|
||||
string? line;
|
||||
while ((line = stdin.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
if (line.Length == 0) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var request = JsonSerializer.Deserialize<Request>(line, jsonOptions);
|
||||
if (request == null)
|
||||
{
|
||||
WriteResponse(new ErrorResponse("Failed to parse request"));
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (request.Cmd?.ToLowerInvariant())
|
||||
{
|
||||
case "ocr":
|
||||
HandleOcr(request, ocrEngine);
|
||||
break;
|
||||
case "screenshot":
|
||||
HandleScreenshot(request);
|
||||
break;
|
||||
case "capture":
|
||||
HandleCapture(request);
|
||||
break;
|
||||
default:
|
||||
WriteResponse(new ErrorResponse($"Unknown command: {request.Cmd}"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteResponse(new ErrorResponse(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
// ── Handlers ────────────────────────────────────────────────────────────────
|
||||
|
||||
void HandleOcr(Request req, OcrEngine engine)
|
||||
{
|
||||
using var bitmap = CaptureScreen(req.Region);
|
||||
var softwareBitmap = BitmapToSoftwareBitmap(bitmap);
|
||||
var result = engine.RecognizeAsync(softwareBitmap).AsTask().GetAwaiter().GetResult();
|
||||
|
||||
var lines = new List<OcrLineResult>();
|
||||
foreach (var ocrLine in result.Lines)
|
||||
{
|
||||
var words = new List<OcrWordResult>();
|
||||
foreach (var word in ocrLine.Words)
|
||||
{
|
||||
words.Add(new OcrWordResult
|
||||
{
|
||||
Text = word.Text,
|
||||
X = (int)Math.Round(word.BoundingRect.X),
|
||||
Y = (int)Math.Round(word.BoundingRect.Y),
|
||||
Width = (int)Math.Round(word.BoundingRect.Width),
|
||||
Height = (int)Math.Round(word.BoundingRect.Height),
|
||||
});
|
||||
}
|
||||
lines.Add(new OcrLineResult { Text = ocrLine.Text, Words = words });
|
||||
}
|
||||
|
||||
WriteResponse(new OcrResponse { Text = result.Text, Lines = lines });
|
||||
}
|
||||
|
||||
void HandleScreenshot(Request req)
|
||||
{
|
||||
if (string.IsNullOrEmpty(req.Path))
|
||||
{
|
||||
WriteResponse(new ErrorResponse("screenshot command requires 'path'"));
|
||||
return;
|
||||
}
|
||||
|
||||
using var bitmap = CaptureScreen(req.Region);
|
||||
var format = GetImageFormat(req.Path);
|
||||
bitmap.Save(req.Path, format);
|
||||
WriteResponse(new OkResponse());
|
||||
}
|
||||
|
||||
void HandleCapture(Request req)
|
||||
{
|
||||
using var bitmap = CaptureScreen(req.Region);
|
||||
using var ms = new MemoryStream();
|
||||
bitmap.Save(ms, ImageFormat.Png);
|
||||
var base64 = Convert.ToBase64String(ms.ToArray());
|
||||
WriteResponse(new CaptureResponse { Image = base64 });
|
||||
}
|
||||
|
||||
// ── Screen Capture ──────────────────────────────────────────────────────────
|
||||
|
||||
Bitmap CaptureScreen(RegionRect? region)
|
||||
{
|
||||
int x, y, w, h;
|
||||
if (region != null)
|
||||
{
|
||||
x = region.X;
|
||||
y = region.Y;
|
||||
w = region.Width;
|
||||
h = region.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Primary monitor only (0,0 origin, SM_CXSCREEN / SM_CYSCREEN)
|
||||
x = 0;
|
||||
y = 0;
|
||||
w = GetSystemMetrics(0); // SM_CXSCREEN
|
||||
h = GetSystemMetrics(1); // SM_CYSCREEN
|
||||
}
|
||||
|
||||
var bitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb);
|
||||
using var g = Graphics.FromImage(bitmap);
|
||||
g.CopyFromScreen(x, y, 0, 0, new Size(w, h), CopyPixelOperation.SourceCopy);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// ── Bitmap → SoftwareBitmap conversion (in-memory) ─────────────────────────
|
||||
|
||||
SoftwareBitmap BitmapToSoftwareBitmap(Bitmap bitmap)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
bitmap.Save(ms, ImageFormat.Bmp);
|
||||
ms.Position = 0;
|
||||
|
||||
var stream = ms.AsRandomAccessStream();
|
||||
var decoder = BitmapDecoder.CreateAsync(stream).AsTask().GetAwaiter().GetResult();
|
||||
var softwareBitmap = decoder.GetSoftwareBitmapAsync().AsTask().GetAwaiter().GetResult();
|
||||
return softwareBitmap;
|
||||
}
|
||||
|
||||
// ── Response writing ────────────────────────────────────────────────────────
|
||||
|
||||
void WriteResponse(object response)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(response, jsonOptions);
|
||||
Console.Out.WriteLine(json);
|
||||
Console.Out.Flush();
|
||||
}
|
||||
|
||||
ImageFormat GetImageFormat(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path).ToLowerInvariant();
|
||||
return ext switch
|
||||
{
|
||||
".jpg" or ".jpeg" => ImageFormat.Jpeg,
|
||||
".bmp" => ImageFormat.Bmp,
|
||||
_ => ImageFormat.Png,
|
||||
};
|
||||
}
|
||||
|
||||
// ── P/Invoke ────────────────────────────────────────────────────────────────
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetProcessDPIAware();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern int GetSystemMetrics(int nIndex);
|
||||
|
||||
// ── Request / Response Models ───────────────────────────────────────────────
|
||||
|
||||
class Request
|
||||
{
|
||||
[JsonPropertyName("cmd")]
|
||||
public string? Cmd { get; set; }
|
||||
|
||||
[JsonPropertyName("region")]
|
||||
public RegionRect? Region { get; set; }
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string? Path { get; set; }
|
||||
}
|
||||
|
||||
class RegionRect
|
||||
{
|
||||
[JsonPropertyName("x")]
|
||||
public int X { get; set; }
|
||||
|
||||
[JsonPropertyName("y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
[JsonPropertyName("width")]
|
||||
public int Width { get; set; }
|
||||
|
||||
[JsonPropertyName("height")]
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
class ReadyResponse
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
public bool Ok => true;
|
||||
|
||||
[JsonPropertyName("ready")]
|
||||
public bool Ready => true;
|
||||
}
|
||||
|
||||
class OkResponse
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
public bool Ok => true;
|
||||
}
|
||||
|
||||
class ErrorResponse(string message)
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
public bool Ok => false;
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string Error => message;
|
||||
}
|
||||
|
||||
class OcrResponse
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
public bool Ok => true;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("lines")]
|
||||
public List<OcrLineResult> Lines { get; set; } = [];
|
||||
}
|
||||
|
||||
class OcrLineResult
|
||||
{
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("words")]
|
||||
public List<OcrWordResult> Words { get; set; } = [];
|
||||
}
|
||||
|
||||
class OcrWordResult
|
||||
{
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("x")]
|
||||
public int X { get; set; }
|
||||
|
||||
[JsonPropertyName("y")]
|
||||
public int Y { get; set; }
|
||||
|
||||
[JsonPropertyName("width")]
|
||||
public int Width { get; set; }
|
||||
|
||||
[JsonPropertyName("height")]
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
class CaptureResponse
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
public bool Ok => true;
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; } = "";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue