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;
+ }
}