diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs index 2bbd6ffca..0368bfdd0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebBrowser.cs @@ -4,7 +4,7 @@ namespace BotSharp.Abstraction.Browsing; public interface IWebBrowser { - Task LaunchBrowser(MessageInfo message); + Task LaunchBrowser(MessageInfo message, BrowserActionArgs args); Task ScreenshotAsync(MessageInfo message, string path); Task ScrollPage(MessageInfo message, PageActionArgs args); diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs index 1a6920c82..21bdf6abc 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionArgs.cs @@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.Browsing.Models; public class BrowserActionArgs { - + public bool Headless { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs index 04ba82b25..edc66781e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionParams.cs @@ -1,5 +1,6 @@ namespace BotSharp.Abstraction.Browsing.Models; +[Obsolete("This class is deprecated, use BrowserActionArgs instead.")] public class BrowserActionParams { public Agent Agent { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs index 63c592b77..b3576b90c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs @@ -8,7 +8,6 @@ public class BrowserActionResult public string Selector { get; set; } public string Body { get; set; } public bool IsHighlighted { get; set; } - public DateTime? ExecutedAt { get; set; } = DateTime.UtcNow; public override string ToString() { diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs index f583febba..8bf09cb10 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs @@ -12,6 +12,9 @@ public class PageActionArgs public string Url { get; set; } = null!; public bool OpenNewTab { get; set; } = false; + public bool WaitForNetworkIdle = true; + public float? Timeout { get; set; } + /// /// On page data fetched /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 8c3a0e81b..d85b3cae8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -23,6 +23,8 @@ public interface IBotSharpRepository User? GetUserByUserName(string userName) => throw new NotImplementedException(); void CreateUser(User user) => throw new NotImplementedException(); void UpdateUserVerified(string userId) => throw new NotImplementedException(); + void UpdateUserVerificationCode(string userId, string verficationCode) => throw new NotImplementedException(); + void UpdateUserPassword(string userId, string password) => throw new NotImplementedException(); #endregion #region Agent diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs index 0de103280..cbc18dd58 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs @@ -9,4 +9,5 @@ public interface IAuthenticationHook void AddClaims(List claims); void BeforeSending(Token token); Task UserCreated(User user); + Task VerificationCodeResetPassword(User user); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 722917cc7..02a7dd9e3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -12,4 +12,6 @@ public interface IUserService Task GetMyProfile(); Task VerifyUserNameExisting(string userName); Task VerifyEmailExisting(string email); + Task SendVerificationCodeResetPassword(User user); + Task ResetUserPassword(User user); } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index c2b309407..03fe8bc42 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -95,6 +95,13 @@ record = user; record = db.GetUserByUserName(id); } + //verify password is correct or not. + var hashPassword = Utilities.HashTextMd5($"{password}{record.Salt}"); + if (hashPassword != record.Password) + { + return default; + } + User? user = record; var isAuthenticatedByHook = false; var hooks = _services.GetServices(); @@ -280,12 +287,16 @@ record = db.GetUserByUserName(id); public async Task VerifyUserNameExisting(string userName) { if (string.IsNullOrEmpty(userName)) + { return true; + } var db = _services.GetRequiredService(); var user = db.GetUserByUserName(userName); if (user != null) + { return true; + } return false; } @@ -293,13 +304,61 @@ public async Task VerifyUserNameExisting(string userName) public async Task VerifyEmailExisting(string email) { if (string.IsNullOrEmpty(email)) + { return true; + } var db = _services.GetRequiredService(); var emailName = db.GetUserByEmail(email); if (emailName != null) + { return true; + } return false; } + + public async Task SendVerificationCodeResetPassword(User user) + { + var db = _services.GetRequiredService(); + var record = db.GetUserByEmail(user.Email); + if (record == null) + { + return false; + } + + record.VerificationCode = Nanoid.Generate(alphabet: "0123456789", size: 6); + + //update current verification code. + db.UpdateUserVerificationCode(record.Id, record.VerificationCode); + + //send code to user Email. + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + hook.VerificationCodeResetPassword(record); + } + + return true; + } + + public async Task ResetUserPassword(User user) + { + var db = _services.GetRequiredService(); + var record = db.GetUserByEmail(user.Email); + + if (record == null) + { + return false; + } + + if (user.VerificationCode != record.VerificationCode) + { + return false; + } + + var newPassword = Utilities.HashTextMd5($"{user.Password}{record.Salt}"); + db.UpdateUserPassword(record.Id, newPassword); + return true; + } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index f0ab46adc..f260fd333 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -108,6 +108,18 @@ public async Task VerifyEmailExisting([FromQuery] string email) { return await _userService.VerifyEmailExisting(email); } + [AllowAnonymous] + [HttpPost("/user/verifycode")] + public async Task SendVerificationCodeResetPassword([FromQuery] UserCreationModel user) + { + return await _userService.SendVerificationCodeResetPassword(user.ToUser()); + } + [AllowAnonymous] + [HttpPost("/user/resetpassword")] + public async Task ResetUserPassword([FromBody] UserResetPasswordModel user) + { + return await _userService.ResetUserPassword(user.ToUser()); + } #region Avatar [HttpPost("/user/avatar")] diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserResetPasswordModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserResetPasswordModel.cs new file mode 100644 index 000000000..8a58820a4 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserResetPasswordModel.cs @@ -0,0 +1,22 @@ +using System.Data; + +namespace BotSharp.OpenAPI.ViewModels.Users; + +public class UserResetPasswordModel +{ + public string? Email { get; set; } + public string? Phone { get; set; } + public string Password { get; set; } = string.Empty; + public string VerificationCode { get; set; } + + public User ToUser() + { + return new User + { + Email = Email, + Phone = Phone, + Password = Password, + VerificationCode = VerificationCode + }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MetaAI/Settings/MetaAiSettings.cs b/src/Plugins/BotSharp.Plugin.MetaAI/Settings/MetaAiSettings.cs index cde5121c5..1c8cb1d9d 100644 --- a/src/Plugins/BotSharp.Plugin.MetaAI/Settings/MetaAiSettings.cs +++ b/src/Plugins/BotSharp.Plugin.MetaAI/Settings/MetaAiSettings.cs @@ -2,5 +2,5 @@ namespace BotSharp.Plugin.MetaAI.Settings; public class MetaAiSettings { - public fastTextSetting fastText { get; set; } + public fastTextSetting fastText { get; set; } = new(); } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 11f482871..4f9e50e41 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -56,4 +56,20 @@ public void UpdateUserVerified(string userId) .Set(x => x.UpdatedTime, DateTime.UtcNow); _dc.Users.UpdateOne(filter, update); } + + public void UpdateUserVerificationCode(string userId, string verficationCode) + { + var filter = Builders.Filter.Eq(x => x.Id, userId); + var update = Builders.Update.Set(x => x.VerificationCode, verficationCode) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + _dc.Users.UpdateOne(filter, update); + } + + public void UpdateUserPassword(string userId, string password) + { + var filter = Builders.Filter.Eq(x => x.Id, userId); + var update = Builders.Update.Set(x => x.Password, password) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + _dc.Users.UpdateOne(filter, update); + } } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs index 69e94f139..151cf5063 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightInstance.cs @@ -1,4 +1,3 @@ -using Microsoft.Playwright; using System.IO; namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver; @@ -21,34 +20,35 @@ public class PlaywrightInstance : IDisposable public IPage GetPage(string id, string? pattern = null) { - InitInstance(id).Wait(); return _contexts[id].Pages.LastOrDefault(); } - public async Task InitInstance(string ctxId) + public async Task GetContext(string ctxId) { - if (_playwright == null) - { - _playwright = await Playwright.CreateAsync(); - } - return await InitContext(ctxId); + return _contexts[ctxId]; } - public async Task InitContext(string ctxId) + public async Task InitContext(string ctxId, BrowserActionArgs args) { if (_contexts.ContainsKey(ctxId)) return _contexts[ctxId]; + if (_playwright == null) + { + _playwright = await Playwright.CreateAsync(); + } + string tempFolderPath = $"{Path.GetTempPath()}\\playwright\\{ctxId}"; _contexts[ctxId] = await _playwright.Chromium.LaunchPersistentContextAsync(tempFolderPath, new BrowserTypeLaunchPersistentContextOptions { -#if DEBUG - Headless = false, -#else - Headless = true, -#endif + Headless = args.Headless, Channel = "chrome", + ViewportSize = new ViewportSize + { + Width = 1600, + Height = 900 + }, IgnoreDefaultArgs = [ "--enable-automation", @@ -71,10 +71,6 @@ public async Task InitContext(string ctxId) Serilog.Log.Information($"Page is closed: {e.Url}"); }; Serilog.Log.Information($"New page is created: {page.Url}"); - if (!page.IsClosed) - { - await page.SetViewportSizeAsync(1600, 900); - } /*page.Response += async (sender, e) => { @@ -99,8 +95,13 @@ public async Task InitContext(string ctxId) public async Task NewPage(string ctxId, DataFetched? fetched) { - await InitContext(ctxId); - var page = await _contexts[ctxId].NewPageAsync(); + var context = await GetContext(ctxId); + var page = await context.NewPageAsync(); + + // 许多网站为了防止信息被爬取,会添加一些防护手段。其中之一是检测 window.navigator.webdriver 属性。 + // 当使用 Playwright 打开浏览器时,该属性会被设置为 true,从而被网站识别为自动化工具。通过以下方式屏蔽这个属性,让网站无法识别是否使用了 Playwright + var js = @"Object.defineProperties(navigator, {webdriver:{get:()=>false}});"; + await page.AddInitScriptAsync(js); if (fetched != null) { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs index 4fb916600..f0b9c7381 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -7,7 +7,7 @@ public partial class PlaywrightWebDriver public async Task GoToPage(MessageInfo message, PageActionArgs args) { var result = new BrowserActionResult(); - var context = await _instance.InitInstance(message.ContextId); + var context = await _instance.GetContext(message.ContextId); try { // Check if the page is already open @@ -29,9 +29,19 @@ public async Task GoToPage(MessageInfo message, PageActionA var page = args.OpenNewTab ? await _instance.NewPage(message.ContextId, fetched: args.OnDataFetched) : _instance.GetPage(message.ContextId); - var response = await page.GotoAsync(args.Url); + var response = await page.GotoAsync(args.Url, new PageGotoOptions + { + Timeout = args.Timeout + }); + await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); - await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + if (args.WaitForNetworkIdle) + { + await page.WaitForLoadStateAsync(LoadState.NetworkIdle, new PageWaitForLoadStateOptions + { + Timeout = args.Timeout + }); + } if (response.Status == 200) { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LaunchBrowser.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LaunchBrowser.cs index b08716f68..d6509fd22 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LaunchBrowser.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LaunchBrowser.cs @@ -2,9 +2,9 @@ namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver; public partial class PlaywrightWebDriver { - public async Task LaunchBrowser(MessageInfo message) + public async Task LaunchBrowser(MessageInfo message, BrowserActionArgs args) { - var context = await _instance.InitInstance(message.ContextId); + var context = await _instance.InitContext(message.ContextId, args); var result = new BrowserActionResult { IsSuccess = context.Pages.Count > 0 diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs index 4bbd84aca..f62c230a7 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs @@ -40,12 +40,13 @@ public async Task LocateElement(MessageInfo message, Elemen // Retrieve the page raw html and infer the element path if (!string.IsNullOrEmpty(location.Text)) { + var text = location.Text.Replace("(", "\\(").Replace(")", "\\)"); var regexExpression = location.MatchRule.ToLower() switch { - "startwith" => $"^{location.Text}", - "endwith" => $"{location.Text}$", - "contains" => $"{location.Text}", - _ => $"^{location.Text}$" + "startwith" => $"^{text}", + "endwith" => $"{text}$", + "contains" => $"{text}", + _ => $"^{text}$" }; var regex = new Regex(regexExpression, RegexOptions.IgnoreCase); locator = locator.GetByText(regex); diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs index 4d8c678f7..4e8b6498a 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/SeleniumDriver/SeleniumWebDriver.LaunchBrowser.cs @@ -2,7 +2,7 @@ namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver; public partial class SeleniumWebDriver { - public async Task LaunchBrowser(MessageInfo message) + public async Task LaunchBrowser(MessageInfo message, BrowserActionArgs args) { var context = await _instance.InitInstance(message.ContextId); var result = new BrowserActionResult() diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs index 67158ea66..6f4c36542 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs @@ -31,7 +31,10 @@ public async Task Execute(RoleDialogModel message) ContextId = convService.ConversationId, MessageId = message.MessageId }; - var result = await _browser.LaunchBrowser(msgInfo); + var result = await _browser.LaunchBrowser(msgInfo, new BrowserActionArgs + { + Headless = false + }); result = await _browser.GoToPage(msgInfo, new PageActionArgs { Url = url