diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs index 8f1cee10..abdfd67b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Enums/BroswerActionEnum.cs @@ -7,4 +7,5 @@ public enum BroswerActionEnum Typing = 3, Hover = 4, Scroll = 5, + DragAndDrop = 6 } diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs index fd95bd66..9a1ef0d3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementPosition.cs @@ -5,4 +5,9 @@ public class ElementPosition public float X { get; set; } = default!; public float Y { get; set; } = default!; + + public override string ToString() + { + return $"[{X}, {Y}]"; + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/EventPriority.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/EventPriority.cs new file mode 100644 index 00000000..75c4402d --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/EventPriority.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Abstraction.Infrastructures.Enums; + +public enum EventPriority +{ + Low = 1, + Medium = 2, + High = 3 +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs index 68ceebd2..f2276c13 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventPublisher.cs @@ -10,7 +10,7 @@ public interface IEventPublisher /// Task BroadcastAsync(string channel, string message); - Task PublishAsync(string channel, string message); + Task PublishAsync(string channel, string message, EventPriority? priority = null); Task ReDispatchAsync(string channel, int count = 10, string order = "asc"); diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs index f295e54e..fa96bb35 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Events/IEventSubscriber.cs @@ -4,5 +4,5 @@ public interface IEventSubscriber { Task SubscribeAsync(string channel, Func received); - Task SubscribeAsync(string channel, string group, Func received); + Task SubscribeAsync(string channel, string group, bool priorityEnabled, Func received); } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs index 2ff585d6..435451d4 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisPublisher.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Infrastructures.Enums; using StackExchange.Redis; namespace BotSharp.Core.Infrastructures.Events; @@ -20,10 +21,16 @@ public class RedisPublisher : IEventPublisher await _subscriber.PublishAsync(channel, message); } - public async Task PublishAsync(string channel, string message) + public async Task PublishAsync(string channel, string message, EventPriority? priority = null) { var db = _redis.GetDatabase(); + // convert to apporiate channel by priority + if (priority != null) + { + channel = $"{channel}-{priority}"; + } + if (CheckMessageExists(db, channel, "message", message)) { _logger.LogError($"The message already exists {channel} {message}"); @@ -41,7 +48,7 @@ public class RedisPublisher : IEventPublisher _logger.LogInformation($"Published message {channel} {message} ({messageId})"); } - private bool CheckMessageExists(IDatabase db, string streamName, string fieldName, string desiredValue) + private bool CheckMessageExists(IDatabase db, string channel, string fieldName, string desiredValue) { // Define the range to fetch all messages RedisValue start = "-"; // Start from the smallest ID @@ -49,7 +56,7 @@ public class RedisPublisher : IEventPublisher int count = 10; // Number of messages to retrieve // Fetch the latest 10 messages - var streamEntries = db.StreamRange(streamName, start, end, count, Order.Descending); + var streamEntries = db.StreamRange(channel, start, end, count, Order.Descending); if (streamEntries.Length == 0) { @@ -84,7 +91,10 @@ public class RedisPublisher : IEventPublisher try { - var messageId = await db.StreamAddAsync(channel, "message", entry.Values[0].Value); + var messageId = await db.StreamAddAsync(channel, [ + new NameValueEntry("message", entry.Values[0].Value), + new NameValueEntry("timestamp", DateTime.UtcNow.ToString("o")) + ]); _logger.LogWarning($"ReDispatched message: {channel} {entry.Values[0].Value} ({messageId})"); diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs index bf6652b1..89b38a8d 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Events/RedisSubscriber.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Infrastructures.Enums; using StackExchange.Redis; namespace BotSharp.Core.Infrastructures.Events; @@ -24,10 +25,72 @@ public class RedisSubscriber : IEventSubscriber }); } - public async Task SubscribeAsync(string channel, string group, Func received) + public async Task SubscribeAsync(string channel, string group, bool priorityEnabled, Func received) { var db = _redis.GetDatabase(); + if (priorityEnabled) + { + await CreateConsumerGroup(db, $"{channel}-{EventPriority.Low}", group); + await CreateConsumerGroup(db, $"{channel}-{EventPriority.Medium}", group); + await CreateConsumerGroup(db, $"{channel}-{EventPriority.High}", group); + } + else + { + await CreateConsumerGroup(db, channel, group); + } + + while (true) + { + await Task.Delay(100); + + if (priorityEnabled) + { + if (await HandleGroupMessage(db, $"{channel}-{EventPriority.High}", group, received) > 0) + { + continue; + } + + if (await HandleGroupMessage(db, $"{channel}-{EventPriority.Medium}", group, received) > 0) + { + continue; + } + + await HandleGroupMessage(db, $"{channel}-{EventPriority.Low}", group, received); + } + else + { + await HandleGroupMessage(db, channel, group, received); + } + } + } + + private async Task HandleGroupMessage(IDatabase db, string channel, string group, Func received) + { + var entries = await db.StreamReadGroupAsync(channel, group, Environment.MachineName, count: 1); + foreach (var entry in entries) + { + _logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}"); + await db.StreamAcknowledgeAsync(channel, group, entry.Id); + + try + { + await received(channel, entry.Values[0].Value); + + // Optionally delete the message to save space + await db.StreamDeleteAsync(channel, [entry.Id]); + } + catch (Exception ex) + { + _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}"); + } + } + + return entries.Length; + } + + private async Task CreateConsumerGroup(IDatabase db, string channel, string group) + { // Create the consumer group if it doesn't exist try { @@ -43,30 +106,5 @@ public class RedisSubscriber : IEventSubscriber _logger.LogError($"Error creating consumer group: '{group}' {ex.Message}"); throw; } - - while (true) - { - var entries = await db.StreamReadGroupAsync(channel, group, Environment.MachineName, count: 1); - foreach (var entry in entries) - { - _logger.LogInformation($"Consumer {Environment.MachineName} received: {channel} {entry.Values[0].Value}"); - await db.StreamAcknowledgeAsync(channel, group, entry.Id); - - try - { - await received(channel, entry.Values[0].Value); - - // Optionally delete the message to save space - await db.StreamDeleteAsync(channel, [entry.Id]); - } - catch (Exception ex) - { - _logger.LogError($"Error processing message: {ex.Message}, event id: {channel} {entry.Id}\r\n{ex}"); - } - } - - await Task.Delay(Random.Shared.Next(1, 11) * 100); - } - } } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs index f7884beb..40fec84e 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.DoAction.cs @@ -79,10 +79,102 @@ public partial class PlaywrightWebDriver { await locator.HoverAsync(); } + else if (action.Action == BroswerActionEnum.DragAndDrop) + { + // Locate the element to drag + var box = await locator.BoundingBoxAsync(); + + if (box != null) + { + // Calculate start position + float startX = box.X + box.Width / 2; // Start at the center of the element + float startY = box.Y + box.Height / 2; + + // Drag offsets + float offsetX = action.Position.X; + // Move horizontally + if (action.Position.Y == 0) + { + // Perform drag-and-move + // Move mouse to the start position + var mouse = page.Mouse; + await mouse.MoveAsync(startX, startY); + await mouse.DownAsync(); + + // Move mouse smoothly in increments + var tracks = GetVelocityTrack(offsetX); + foreach (var track in tracks) + { + startX += track; + await page.Mouse.MoveAsync(startX, 0, new MouseMoveOptions + { + Steps = 3 + }); + } + + // Release mouse button + await mouse.UpAsync(); + } + else + { + throw new NotImplementedException(); + } + } + } if (action.WaitTime > 0) { await Task.Delay(1000 * action.WaitTime); } } + + public static List GetVelocityTrack(float distance) + { + // Initialize the track list to store the movement distances + List track = new List(); + + // Initialize variables + float current = 0; // Current position + float mid = distance * 4 / 5; // Deceleration threshold + float t = 0.2f; // Time interval + float v = 1; // Initial velocity + + // Generate the track + while (current < distance) + { + float a; // Acceleration + + // Determine acceleration based on position + if (current < mid) + { + a = 4; // Accelerate + } + else + { + a = -3; // Decelerate + } + + // Calculate new velocity + float v0 = v; + v = v0 + a * t; + + // Calculate the movement during this interval + float move = v0 * t + 0.5f * a * t * t; + + // Update current position + if (current + move > distance) + { + move = distance - current; + track.Add((int)Math.Round(move)); + break; + } + + current += move; + + // Add rounded movement to the track + track.Add((int)Math.Round(move)); + } + + return track; + } }