commit
9d25ea627a
|
|
@ -4,7 +4,7 @@ namespace BotSharp.Abstraction.Browsing;
|
|||
|
||||
public interface IWebBrowser
|
||||
{
|
||||
Task<BrowserActionResult> LaunchBrowser(MessageInfo message);
|
||||
Task<BrowserActionResult> LaunchBrowser(MessageInfo message, BrowserActionArgs args);
|
||||
Task<BrowserActionResult> ScreenshotAsync(MessageInfo message, string path);
|
||||
Task<BrowserActionResult> ScrollPage(MessageInfo message, PageActionArgs args);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.Browsing.Models;
|
|||
|
||||
public class BrowserActionArgs
|
||||
{
|
||||
|
||||
public bool Headless { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// On page data fetched
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ public interface IAuthenticationHook
|
|||
void AddClaims(List<Claim> claims);
|
||||
void BeforeSending(Token token);
|
||||
Task UserCreated(User user);
|
||||
Task VerificationCodeResetPassword(User user);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,6 @@ public interface IUserService
|
|||
Task<User> GetMyProfile();
|
||||
Task<bool> VerifyUserNameExisting(string userName);
|
||||
Task<bool> VerifyEmailExisting(string email);
|
||||
Task<bool> SendVerificationCodeResetPassword(User user);
|
||||
Task<bool> ResetUserPassword(User user);
|
||||
}
|
||||
|
|
@ -95,6 +95,13 @@ public class UserService : IUserService
|
|||
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<IAuthenticationHook>();
|
||||
|
|
@ -280,12 +287,16 @@ public class UserService : IUserService
|
|||
public async Task<bool> VerifyUserNameExisting(string userName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var user = db.GetUserByUserName(userName);
|
||||
if (user != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -293,13 +304,61 @@ public class UserService : IUserService
|
|||
public async Task<bool> VerifyEmailExisting(string email)
|
||||
{
|
||||
if (string.IsNullOrEmpty(email))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var emailName = db.GetUserByEmail(email);
|
||||
if (emailName != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> SendVerificationCodeResetPassword(User user)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
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<IAuthenticationHook>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
hook.VerificationCodeResetPassword(record);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ResetUserPassword(User user)
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,18 @@ public class UserController : ControllerBase
|
|||
{
|
||||
return await _userService.VerifyEmailExisting(email);
|
||||
}
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/user/verifycode")]
|
||||
public async Task<bool> SendVerificationCodeResetPassword([FromQuery] UserCreationModel user)
|
||||
{
|
||||
return await _userService.SendVerificationCodeResetPassword(user.ToUser());
|
||||
}
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/user/resetpassword")]
|
||||
public async Task<bool> ResetUserPassword([FromBody] UserResetPasswordModel user)
|
||||
{
|
||||
return await _userService.ResetUserPassword(user.ToUser());
|
||||
}
|
||||
|
||||
#region Avatar
|
||||
[HttpPost("/user/avatar")]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -2,5 +2,5 @@ namespace BotSharp.Plugin.MetaAI.Settings;
|
|||
|
||||
public class MetaAiSettings
|
||||
{
|
||||
public fastTextSetting fastText { get; set; }
|
||||
public fastTextSetting fastText { get; set; } = new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,4 +56,20 @@ public partial class MongoRepository
|
|||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
_dc.Users.UpdateOne(filter, update);
|
||||
}
|
||||
|
||||
public void UpdateUserVerificationCode(string userId, string verficationCode)
|
||||
{
|
||||
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
|
||||
var update = Builders<UserDocument>.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<UserDocument>.Filter.Eq(x => x.Id, userId);
|
||||
var update = Builders<UserDocument>.Update.Set(x => x.Password, password)
|
||||
.Set(x => x.UpdatedTime, DateTime.UtcNow);
|
||||
_dc.Users.UpdateOne(filter, update);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IBrowserContext> InitInstance(string ctxId)
|
||||
public async Task<IBrowserContext> GetContext(string ctxId)
|
||||
{
|
||||
return _contexts[ctxId];
|
||||
}
|
||||
|
||||
public async Task<IBrowserContext> InitContext(string ctxId, BrowserActionArgs args)
|
||||
{
|
||||
if (_contexts.ContainsKey(ctxId))
|
||||
return _contexts[ctxId];
|
||||
|
||||
if (_playwright == null)
|
||||
{
|
||||
_playwright = await Playwright.CreateAsync();
|
||||
}
|
||||
return await InitContext(ctxId);
|
||||
}
|
||||
|
||||
public async Task<IBrowserContext> InitContext(string ctxId)
|
||||
{
|
||||
if (_contexts.ContainsKey(ctxId))
|
||||
return _contexts[ctxId];
|
||||
|
||||
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 class PlaywrightInstance : IDisposable
|
|||
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 class PlaywrightInstance : IDisposable
|
|||
|
||||
public async Task<IPage> 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ public partial class PlaywrightWebDriver
|
|||
public async Task<BrowserActionResult> 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 partial class PlaywrightWebDriver
|
|||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
|
|||
|
||||
public partial class PlaywrightWebDriver
|
||||
{
|
||||
public async Task<BrowserActionResult> LaunchBrowser(MessageInfo message)
|
||||
public async Task<BrowserActionResult> 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
|
||||
|
|
|
|||
|
|
@ -40,12 +40,13 @@ public partial class PlaywrightWebDriver
|
|||
// 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);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ namespace BotSharp.Plugin.WebDriver.Drivers.SeleniumDriver;
|
|||
|
||||
public partial class SeleniumWebDriver
|
||||
{
|
||||
public async Task<BrowserActionResult> LaunchBrowser(MessageInfo message)
|
||||
public async Task<BrowserActionResult> LaunchBrowser(MessageInfo message, BrowserActionArgs args)
|
||||
{
|
||||
var context = await _instance.InitInstance(message.ContextId);
|
||||
var result = new BrowserActionResult()
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@ public class OpenBrowserFn : IFunctionCallback
|
|||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue