diff --git a/.dockerignore b/.dockerignore index eb563a67d..2bae9e5fe 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,13 @@ -openbullet2-web-client/node_modules \ No newline at end of file +Changelog/ +.github +.git +.gitignore +.dockerignore +Dockerfile +Dockerfile.build +Dockerfile.remote +docker-compose.yml +CONTRIBUTORS.md +LICENSE +README.md +openbullet2-web-client/node_modules diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index bdb91c0eb..535b94a26 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -14,26 +14,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 # Read the version - name: Read version run: echo "VERSION=$(cat OpenBullet2.Web/version.txt)" >> $GITHUB_ENV - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: push: true file: ./Dockerfile.remote diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 368f90d42..c7b51c0de 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -12,10 +12,12 @@ jobs: if: contains(github.event.head_commit.message, '[build]') steps: - uses: actions/checkout@v4 + - name: Setup .NET 8 uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x + - name: Unit and integration tests run: dotnet test diff --git a/.github/workflows/build-staging.yml b/.github/workflows/build-staging.yml index 629fb5f02..a1823646e 100644 --- a/.github/workflows/build-staging.yml +++ b/.github/workflows/build-staging.yml @@ -12,6 +12,7 @@ jobs: if: contains(github.event.head_commit.message, '[build]') steps: - uses: actions/checkout@v4 + - name: Setup .NET 8 uses: actions/setup-dotnet@v4 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index b0daebb02..79cddfd59 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -11,13 +11,19 @@ on: - staging jobs: - test: - runs-on: ubuntu-latest + run-tests: + strategy: + matrix: + os: ["ubuntu-latest", "macos-latest", "windows-latest"] + + runs-on: "${{ matrix.os }}" steps: - uses: actions/checkout@v4 + - name: Setup .NET 8 uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x + - name: Unit and integration tests run: dotnet test diff --git a/Dockerfile b/Dockerfile index 460d40e8c..ca0c5aca7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,19 +51,35 @@ COPY --from=frontend /build ./wwwroot COPY OpenBullet2.Web/dbip-country-lite.mmdb . # Install dependencies -RUN apt-get update -yq && apt-get install -y --no-install-recommends apt-utils -RUN apt-get upgrade -yq && apt-get install -yq apt-utils curl git nano wget unzip python3 python3-pip +RUN echo "deb http://deb.debian.org/debian/ unstable main contrib non-free" >> /etc/apt/sources.list.d/debian.list \ + && apt-get update -yq \ + && apt-get install -y --no-install-recommends \ + apt-utils \ + && apt-get upgrade -yq \ + && apt-get install -yq \ + curl \ + wget \ + unzip \ + git \ + python3 \ + python3-pip # Setup nodejs -RUN curl -sL https://deb.nodesource.com/setup_current.x | bash - && apt-get install -yq nodejs build-essential -RUN echo "deb http://deb.debian.org/debian/ unstable main contrib non-free" >> /etc/apt/sources.list.d/debian.list +RUN curl -sL https://deb.nodesource.com/setup_current.x | bash - \ + && apt-get install -yq \ + nodejs \ + build-essential # Install chromium and firefox for selenium and puppeteer -RUN apt-get update -yq && apt-get install -y --no-install-recommends firefox chromium -RUN pip3 install webdrivermanager || true -RUN webdrivermanager firefox chrome --linkpath /usr/local/bin || true - -RUN apt-get clean && rm -rf /var/lib/apt/lists/* +RUN apt-get update -yq && apt-get install -y --no-install-recommends firefox chromium \ + && pip3 install webdrivermanager || true \ + && webdrivermanager firefox chrome --linkpath /usr/local/bin || true + +# Clean up +RUN apt-get remove curl wget unzip --yes \ +&& apt-get clean autoclean --yes \ +&& apt-get autoremove --yes \ +&& rm -rf /var/cache/apt/archives* /var/lib/apt/lists/* EXPOSE 5000 CMD ["dotnet", "./OpenBullet2.Web.dll", "--urls=http://*:5000"] diff --git a/Dockerfile.remote b/Dockerfile.remote index badd3af50..7602e8cfa 100644 --- a/Dockerfile.remote +++ b/Dockerfile.remote @@ -1,50 +1,56 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim -ENV DEBIAN_FRONTEND=noninteractive +ENV DEBIAN_FRONTEND=noninteractive \ + LC_ALL="en_US.UTF-8" \ + LANG="en_US.UTF-8" \ + LANGUAGE="en_US:en" # Install basic dependencies RUN apt-get update -yq \ - && apt-get install -y --no-install-recommends \ - apt-utils \ - curl \ - git \ - nano \ - wget \ - unzip \ - python3 \ - python3-pip \ - gnupg \ - lsb-release \ - software-properties-common + && apt-get install -y --no-install-recommends \ + curl \ + wget \ + unzip \ + gnupg \ + git \ + python3 \ + python3-pip \ + lsb-release \ + software-properties-common # Setup Node.js (LTS Version) RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - \ - && apt-get install -yq nodejs build-essential + && apt-get install -yq \ + nodejs \ + build-essential # Add Mozilla Team PPA for the latest Firefox RUN echo "deb http://ppa.launchpad.net/mozillateam/ppa/ubuntu focal main" | tee /etc/apt/sources.list.d/mozillateam-ppa.list \ - && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A6DCF7707EBC211F \ - && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9BDB3D89CE49EC21 \ - && apt-get update -yq + && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A6DCF7707EBC211F \ + && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9BDB3D89CE49EC21 \ + && apt-get update -yq # Install the latest Firefox and Chromium RUN apt-get install -y --no-install-recommends \ - firefox \ - chromium + firefox \ + chromium # Install WebDriverManager RUN pip3 install webdrivermanager || true \ - && webdrivermanager firefox chrome --linkpath /usr/local/bin || true - -# Clean up -RUN apt-get clean && rm -rf /var/lib/apt/lists/* + && webdrivermanager firefox chrome --linkpath /usr/local/bin || true WORKDIR /app # Download and unpack the latest OpenBullet2.Web release RUN wget https://github.com/openbullet/openbullet2/releases/latest/download/OpenBullet2.Web.zip \ - && unzip OpenBullet2.Web.zip \ - && rm OpenBullet2.Web.zip + && unzip OpenBullet2.Web.zip \ + && rm OpenBullet2.Web.zip + +# Clean up +RUN apt-get remove curl wget unzip gnupg --yes \ +&& apt-get clean autoclean --yes \ +&& apt-get autoremove --yes \ +&& rm -rf /var/cache/apt/archives* /var/lib/apt/lists/* EXPOSE 5000 diff --git a/OpenBullet2.Web/Program.cs b/OpenBullet2.Web/Program.cs index c20fadb0f..e15a4936c 100644 --- a/OpenBullet2.Web/Program.cs +++ b/OpenBullet2.Web/Program.cs @@ -130,7 +130,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(service => - new HybridWordlistRepository(service.GetService(), + new HybridWordlistRepository(service.GetRequiredService(), $"{Globals.UserDataFolder}/Wordlists")); builder.Services.AddScoped(); @@ -143,7 +143,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(service => - new DiskConfigRepository(service.GetService(), + new DiskConfigRepository(service.GetRequiredService(), $"{Globals.UserDataFolder}/Configs")); builder.Services.AddSingleton(); builder.Services.AddSingleton(service => @@ -155,7 +155,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(service => - new JobMonitorService(service.GetService(), + new JobMonitorService(service.GetRequiredService(), $"{Globals.UserDataFolder}/triggeredActions.json", false)); builder.Services.AddSingleton(); builder.Services.AddSingleton(_ => new RuriLibSettingsService(Globals.UserDataFolder)); @@ -166,7 +166,7 @@ _ => new IntoliRandomUAProvider("user-agents.json")); builder.Services.AddSingleton(); builder.Services.AddSingleton(service => - new FileJobLogger(service.GetService(), + new FileJobLogger(service.GetRequiredService(), $"{Globals.UserDataFolder}/Logs/Jobs")); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/OpenBullet2.Web/Utils/BlockMapper.cs b/OpenBullet2.Web/Utils/BlockMapper.cs index 7874d0397..05ecd44a8 100644 --- a/OpenBullet2.Web/Utils/BlockMapper.cs +++ b/OpenBullet2.Web/Utils/BlockMapper.cs @@ -10,14 +10,15 @@ using RuriLib.Models.Blocks.Settings; using RuriLib.Models.Blocks.Settings.Interpolated; using System.Text.Json; +using OpenBullet2.Web.Exceptions; namespace OpenBullet2.Web.Utils; -static internal class BlockMapper +internal static class BlockMapper { // Here I did the dto -> block mappings manually while I find // a way to make automapper work properly on BlockSetting... - static internal List MapStack( + internal static List MapStack( this List jsonElements, IMapper mapper) { var stack = new List(); @@ -28,6 +29,11 @@ static internal List MapStack( var id = jsonElement.GetProperty("id").GetString(); BlockInstance block; + if (id is null) + { + throw new MappingException("Block id not found"); + } + switch (id) { case "HttpRequest": @@ -212,7 +218,7 @@ private static void MapMultipartSettings(MultipartRequestParamsDto dto, foreach (var c in dto.Contents) { var contentDto = PolyMapper.ConvertPolyDto( - (JsonElement)c!); + (JsonElement)c); HttpContentSettingsGroup content; @@ -269,44 +275,44 @@ private static void MapSetting(BlockSettingDto? dto, BlockSetting setting) } setting.InputMode = dto.InputMode; - setting.InputVariableName = dto.InputVariableName; + setting.InputVariableName = dto.InputVariableName ?? string.Empty; var value = (JsonElement)dto.Value!; switch (dto.Type) { case BlockSettingType.String: - ((StringSetting)setting.FixedSetting).Value = value.GetString(); - ((InterpolatedStringSetting)setting.InterpolatedSetting).Value = value.GetString(); + ((StringSetting)setting.FixedSetting!).Value = value.GetString(); + ((InterpolatedStringSetting)setting.InterpolatedSetting!).Value = value.GetString(); break; case BlockSettingType.Int: - ((IntSetting)setting.FixedSetting).Value = value.GetInt32(); + ((IntSetting)setting.FixedSetting!).Value = value.GetInt32(); break; case BlockSettingType.Float: - ((FloatSetting)setting.FixedSetting).Value = value.GetSingle(); + ((FloatSetting)setting.FixedSetting!).Value = value.GetSingle(); break; case BlockSettingType.Bool: - ((BoolSetting)setting.FixedSetting).Value = value.GetBoolean(); + ((BoolSetting)setting.FixedSetting!).Value = value.GetBoolean(); break; case BlockSettingType.ByteArray: - ((ByteArraySetting)setting.FixedSetting).Value = value.GetBytesFromBase64(); + ((ByteArraySetting)setting.FixedSetting!).Value = value.GetBytesFromBase64(); break; case BlockSettingType.ListOfStrings: - ((ListOfStringsSetting)setting.FixedSetting).Value = value - .Deserialize>(Globals.JsonOptions); - ((InterpolatedListOfStringsSetting)setting.InterpolatedSetting).Value = value - .Deserialize>(Globals.JsonOptions); + ((ListOfStringsSetting)setting.FixedSetting!).Value = value + .Deserialize>(Globals.JsonOptions)!; + ((InterpolatedListOfStringsSetting)setting.InterpolatedSetting!).Value = value + .Deserialize>(Globals.JsonOptions)!; break; case BlockSettingType.DictionaryOfStrings: - ((DictionaryOfStringsSetting)setting.FixedSetting).Value = value - .Deserialize>(Globals.JsonOptions); - ((InterpolatedDictionaryOfStringsSetting)setting.InterpolatedSetting).Value = value - .Deserialize>(Globals.JsonOptions); + ((DictionaryOfStringsSetting)setting.FixedSetting!).Value = value + .Deserialize>(Globals.JsonOptions)!; + ((InterpolatedDictionaryOfStringsSetting)setting.InterpolatedSetting!).Value = value + .Deserialize>(Globals.JsonOptions)!; break; case BlockSettingType.Enum: diff --git a/RuriLib.Http/Helpers/ChunkedDecoderOptimized.cs b/RuriLib.Http/Helpers/ChunkedDecoderOptimized.cs index b8387f5e3..c8248da95 100644 --- a/RuriLib.Http/Helpers/ChunkedDecoderOptimized.cs +++ b/RuriLib.Http/Helpers/ChunkedDecoderOptimized.cs @@ -50,13 +50,12 @@ private void ParseNewChunk(ref ReadOnlySequence buff) } } - private int GetChunkLength(ref ReadOnlySequence buff) + private static int GetChunkLength(ref ReadOnlySequence buff) { if (buff.IsSingleSegment) { - var index = -1; var span = buff.FirstSpan; - index = span.IndexOf(_crlfBytes); + var index = span.IndexOf(_crlfBytes); if (index == -1) { diff --git a/RuriLib.Http/RuriLib.Http.csproj b/RuriLib.Http/RuriLib.Http.csproj index 6ab521839..6b36eb660 100644 --- a/RuriLib.Http/RuriLib.Http.csproj +++ b/RuriLib.Http/RuriLib.Http.csproj @@ -6,7 +6,7 @@ Ruri 1.0.1 This is a library that provides a custom HTTP client, in addition to an HttpMessageHandler to be used with the default HttpClient of System.Net - Ruri 2022 + Ruri 2024 https://github.com/openbullet/OpenBullet2/tree/master/RuriLib.Http http; proxy; socks; client; custom; enable diff --git a/RuriLib.Parallelization.Tests/ParallelizerTests.cs b/RuriLib.Parallelization.Tests/ParallelizerTests.cs index daedea04c..b395ff954 100644 --- a/RuriLib.Parallelization.Tests/ParallelizerTests.cs +++ b/RuriLib.Parallelization.Tests/ParallelizerTests.cs @@ -216,7 +216,7 @@ public async Task Run_DecreaseConcurrentThreads_CompleteSlower() [Fact] public async Task Run_PauseAndResume_CompleteAll() { - var count = 10; + const int count = 10; var parallelizer = ParallelizerFactory.Create( type: _type, workItems: Enumerable.Range(1, count), diff --git a/RuriLib.Parallelization/RuriLib.Parallelization.csproj b/RuriLib.Parallelization/RuriLib.Parallelization.csproj index b06e1c1a7..85ac8ca68 100644 --- a/RuriLib.Parallelization/RuriLib.Parallelization.csproj +++ b/RuriLib.Parallelization/RuriLib.Parallelization.csproj @@ -4,7 +4,7 @@ net8.0 Ruri This is a library that can perform multiple tasks (yes, a lot, even infinitely many) that act on some input and return a certain output. - Ruri 2022 + Ruri 2024 https://github.com/openbullet/OpenBullet2/tree/master/RuriLib.Parallelization https://github.com/openbullet/OpenBullet2/tree/master/RuriLib.Parallelization parallelization; task; tasks; manager; taskmanager; multithreading; parallel; multithread; async; diff --git a/RuriLib.Proxies/Clients/HttpProxyClient.cs b/RuriLib.Proxies/Clients/HttpProxyClient.cs index 39e32cd4b..00649f19d 100644 --- a/RuriLib.Proxies/Clients/HttpProxyClient.cs +++ b/RuriLib.Proxies/Clients/HttpProxyClient.cs @@ -14,11 +14,11 @@ namespace RuriLib.Proxies.Clients; /// /// A client that provides proxies connections via HTTP proxies. /// -public class HttpProxyClient : ProxyClient +public partial class HttpProxyClient : ProxyClient { /// /// The HTTP version to send in the first line of the request to the proxy. - /// By default it's 1.1 + /// By default, it's 1.1 /// public string ProtocolVersion { get; set; } = "1.1"; @@ -59,7 +59,7 @@ protected override async Task CreateConnectionAsync(TcpClient client, string des { client.Close(); - if (ex is IOException || ex is SocketException) + if (ex is IOException or SocketException) { throw new ProxyException("Error while working with proxy", ex); } @@ -137,7 +137,7 @@ private static async Task ReceiveResponseAsync(NetworkStream nSt } // Check if the response is a correct HTTP response - var match = Regex.Match(response, "HTTP/[0-9\\.]* ([0-9]{3})"); + var match = HttpResponseRegex().Match(response); if (!match.Success) { @@ -151,4 +151,7 @@ private static async Task ReceiveResponseAsync(NetworkStream nSt return statusCode; } + + [GeneratedRegex("HTTP/[0-9\\.]* ([0-9]{3})")] + private static partial Regex HttpResponseRegex(); } diff --git a/RuriLib.Proxies/RuriLib.Proxies.csproj b/RuriLib.Proxies/RuriLib.Proxies.csproj index e6ca5c9fb..52d2df6be 100644 --- a/RuriLib.Proxies/RuriLib.Proxies.csproj +++ b/RuriLib.Proxies/RuriLib.Proxies.csproj @@ -2,7 +2,7 @@ net8.0 - Ruri 2022 + Ruri 2024 https://github.com/openbullet/OpenBullet2/tree/master/RuriLib.Proxies This is a library that can proxy a `TcpClient` via a proxy server. Supported protocols: HTTP(S), SOCKS4, SOCKS4a, SOCKS5, No proxy proxy; proxies; http; socks; socks4; socks4a; socks5; tcpclient; diff --git a/RuriLib/Attributes/Block.cs b/RuriLib/Attributes/Block.cs index 76092d978..a37b278ef 100644 --- a/RuriLib/Attributes/Block.cs +++ b/RuriLib/Attributes/Block.cs @@ -1,36 +1,38 @@ using System; -namespace RuriLib.Attributes +namespace RuriLib.Attributes; + +/// +/// Attribute used to decorate a method that can be turned into an auto block. +/// +[AttributeUsage(AttributeTargets.Method)] +public class Block : Attribute { /// - /// Attribute used to decorate a method that can be turned into an auto block. + /// The name of the block. If not specified, a name will be automatically + /// generated from the name of the method. /// - [AttributeUsage(AttributeTargets.Method)] - public class Block : Attribute - { - /// - /// The name of the block. If not specified, a name will be automatically - /// generated from the name of the method. - /// - public string name = null; + // ReSharper disable once InconsistentNaming + public string? name = null; - /// - /// The description of what the block does. - /// - public string description = null; + /// + /// The description of what the block does. + /// + // ReSharper disable once InconsistentNaming + public string description; - /// - /// Any extra information that is too long to fit the short and concise description. - /// - public string extraInfo = null; + /// + /// Any extra information that is too long to fit the short and concise description. + /// + // ReSharper disable once InconsistentNaming + public string? extraInfo = null; - /// - /// Creates a attribute given the of what the block does. - /// The name of the block will be automatically generated unless explicitly set in the field. - /// - public Block(string description) - { - this.description = description; - } + /// + /// Creates a attribute given the of what the block does. + /// The name of the block will be automatically generated unless explicitly set in the field. + /// + public Block(string description) + { + this.description = description; } } diff --git a/RuriLib/Attributes/BlockAction.cs b/RuriLib/Attributes/BlockAction.cs index b6363cdba..69c77713b 100644 --- a/RuriLib/Attributes/BlockAction.cs +++ b/RuriLib/Attributes/BlockAction.cs @@ -1,44 +1,46 @@ using System; -namespace RuriLib.Attributes +namespace RuriLib.Attributes; + +/// +/// Attribute used to decorate a method that is a block action. The method should take only one +/// parameter of type . +/// +[AttributeUsage(AttributeTargets.Method)] +public class BlockAction : Attribute { /// - /// Attribute used to decorate a method that is a block action. The method should take only one - /// parameter of type . + /// The name of the action. If not specified, a name will automatically be + /// generated from the name of the method. /// - [AttributeUsage(AttributeTargets.Method)] - public class BlockAction : Attribute - { - /// - /// The name of the action. If not specified, a name will automatically be - /// generated from the name of the method. - /// - public string name = null; + // ReSharper disable once InconsistentNaming + public string? name; - /// - /// The description of what the action does. - /// - public string description = null; + /// + /// The description of what the action does. + /// + // ReSharper disable once InconsistentNaming + public string? description; - /// - /// The id of the block to which this action belongs to. Normally, the - /// id of a block is the name of the method. - /// - public string parentBlockId = null; + /// + /// The id of the block to which this action belongs to. Normally, the + /// id of a block is the name of the method. + /// + // ReSharper disable once InconsistentNaming + public string parentBlockId; - /// - /// Defines a block action. - /// - /// The id of the block to which this action belongs to. Normally, the - /// id of a block is the name of the method. - /// The name of the action. If not specified, a name will automatically be - /// generated from the name of the method. - /// The description of what the action does. - public BlockAction(string parentBlockId, string name = null, string description = null) - { - this.parentBlockId = parentBlockId; - this.name = name; - this.description = description; - } + /// + /// Defines a block action. + /// + /// The id of the block to which this action belongs to. Normally, the + /// id of a block is the name of the method. + /// The name of the action. If not specified, a name will automatically be + /// generated from the name of the method. + /// The description of what the action does. + public BlockAction(string parentBlockId, string? name = null, string? description = null) + { + this.parentBlockId = parentBlockId; + this.name = name; + this.description = description; } } diff --git a/RuriLib/Attributes/BlockCategory.cs b/RuriLib/Attributes/BlockCategory.cs index e1839978a..f138b4a72 100644 --- a/RuriLib/Attributes/BlockCategory.cs +++ b/RuriLib/Attributes/BlockCategory.cs @@ -1,44 +1,47 @@ using System; -namespace RuriLib.Attributes +namespace RuriLib.Attributes; + +/// +/// Attribute used to decorate a class that contains methods decorated with the attribute. +/// +[AttributeUsage(AttributeTargets.Class)] +public class BlockCategory : Attribute { /// - /// Attribute used to decorate a class that contains methods decorated with the attribute. + /// The name of the category. /// - [AttributeUsage(AttributeTargets.Class)] - public class BlockCategory : Attribute - { - /// - /// The name of the category. - /// - public string name = null; + // ReSharper disable once InconsistentNaming + public string name; - /// - /// The common features of blocks that are grouped in this category. - /// - public string description; + /// + /// The common features of blocks that are grouped in this category. + /// + // ReSharper disable once InconsistentNaming + public string description; - /// - /// The background color of the category when displayed in a UI, as an HTML color string. - /// - public string backgroundColor; + /// + /// The background color of the category when displayed in a UI, as an HTML color string. + /// + // ReSharper disable once InconsistentNaming + public string backgroundColor; - /// - /// The foreground color of the category when displayed in a UI, as an HTML color string. - /// - public string foregroundColor; + /// + /// The foreground color of the category when displayed in a UI, as an HTML color string. + /// + // ReSharper disable once InconsistentNaming + public string foregroundColor; - /// - /// Creates a attribute given its , - /// and colors. - /// - public BlockCategory(string name, string description, string backgroundColor = "#fff", - string foregroundColor = "#000") - { - this.name = name; - this.description = description; - this.backgroundColor = backgroundColor; - this.foregroundColor = foregroundColor; - } + /// + /// Creates a attribute given its , + /// and colors. + /// + public BlockCategory(string name, string description, string backgroundColor = "#fff", + string foregroundColor = "#000") + { + this.name = name; + this.description = description; + this.backgroundColor = backgroundColor; + this.foregroundColor = foregroundColor; } } diff --git a/RuriLib/Attributes/BlockImage.cs b/RuriLib/Attributes/BlockImage.cs index ca0fc33f5..9bfe418df 100644 --- a/RuriLib/Attributes/BlockImage.cs +++ b/RuriLib/Attributes/BlockImage.cs @@ -1,34 +1,36 @@ using System; -namespace RuriLib.Attributes +namespace RuriLib.Attributes; + +/// +/// Attribute used to decorate a block that can display images. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class BlockImage : Attribute { /// - /// Attribute used to decorate a block that can display images. + /// The unique id of the image. /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class BlockImage : Attribute - { - /// - /// The unique id of the image. - /// - public string id = null; + // ReSharper disable once InconsistentNaming + public string id; - /// - /// The max width in pixels. - /// - public int maxWidth = 300; + /// + /// The max width in pixels. + /// + // ReSharper disable once InconsistentNaming + public int maxWidth = 300; - /// - /// The max height in pixels. - /// - public int maxHeight = 300; + /// + /// The max height in pixels. + /// + // ReSharper disable once InconsistentNaming + public int maxHeight = 300; - /// - /// Defines a block image with a given . - /// - public BlockImage(string id) - { - this.id = id; - } + /// + /// Defines a block image with a given . + /// + public BlockImage(string id) + { + this.id = id; } } diff --git a/RuriLib/Attributes/BlockParam.cs b/RuriLib/Attributes/BlockParam.cs index 41efe61d6..5caf38bc3 100644 --- a/RuriLib/Attributes/BlockParam.cs +++ b/RuriLib/Attributes/BlockParam.cs @@ -1,35 +1,36 @@ using System; -namespace RuriLib.Attributes +namespace RuriLib.Attributes; + +/// +/// Attribute used to decorate a parameter of a method decorated with the +/// attribute that can add information about the parameter. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public class BlockParam : Attribute { /// - /// Attribute used to decorate a parameter of a method decorated with the - /// attribute that can add information about the parameter. + /// The name of the parameter. If not specified, a name will automatically be + /// generated from the name of the parameter. /// - [AttributeUsage(AttributeTargets.Parameter)] - public class BlockParam : Attribute - { - /// - /// The name of the parameter. If not specified, a name will automatically be - /// generated from the name of the parameter. - /// - public string name = null; + // ReSharper disable once InconsistentNaming + public string? name; - /// - /// The description of what the parameter does. - /// - public string description = null; + /// + /// The description of what the parameter does. + /// + // ReSharper disable once InconsistentNaming + public string? description; - /// - /// Provides additional information to a block parameter. - /// - /// The name of the parameter. If not specified, a name will automatically be - /// generated from the name of the parameter. - /// The description of what the parameter does. - public BlockParam(string name = null, string description = null) - { - this.name = name; - this.description = description; - } + /// + /// Provides additional information to a block parameter. + /// + /// The name of the parameter. If not specified, a name will automatically be + /// generated from the name of the parameter. + /// The description of what the parameter does. + public BlockParam(string? name = null, string? description = null) + { + this.name = name; + this.description = description; } } diff --git a/RuriLib/Attributes/Interpolated.cs b/RuriLib/Attributes/Interpolated.cs index 6f51eca4e..a5461bf85 100644 --- a/RuriLib/Attributes/Interpolated.cs +++ b/RuriLib/Attributes/Interpolated.cs @@ -1,17 +1,10 @@ using System; -namespace RuriLib.Attributes -{ - /// - /// Attribute used to decorate a parameter of a block method to indicate it should be initialized - /// as a setting of type interpolated. - /// - [AttributeUsage(AttributeTargets.Parameter)] - public class Interpolated : Attribute - { - public Interpolated() - { +namespace RuriLib.Attributes; - } - } -} +/// +/// Attribute used to decorate a parameter of a block method to indicate it should be initialized +/// as a setting of type interpolated. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public class Interpolated : Attribute; diff --git a/RuriLib/Attributes/MultiLine.cs b/RuriLib/Attributes/MultiLine.cs index fdc7fb2cc..222a8cd9b 100644 --- a/RuriLib/Attributes/MultiLine.cs +++ b/RuriLib/Attributes/MultiLine.cs @@ -1,17 +1,10 @@ using System; -namespace RuriLib.Attributes -{ - /// - /// Attribute used to decorate a string parameter of a block method to indicate it should be rendered - /// with a multi-line textbox. - /// - [AttributeUsage(AttributeTargets.Parameter)] - public class MultiLine : Attribute - { - public MultiLine() - { +namespace RuriLib.Attributes; - } - } -} +/// +/// Attribute used to decorate a string parameter of a block method to indicate it should be rendered +/// with a multi-line textbox. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public class MultiLine : Attribute; diff --git a/RuriLib/Attributes/Variable.cs b/RuriLib/Attributes/Variable.cs index e9c40d5b2..db44257b6 100644 --- a/RuriLib/Attributes/Variable.cs +++ b/RuriLib/Attributes/Variable.cs @@ -1,27 +1,33 @@ using System; -namespace RuriLib.Attributes +namespace RuriLib.Attributes; + +/// +/// Attribute used to decorate a parameter of a block method to indicate it should be initialized +/// as a setting of type variable, optionally with the given default variable name. +/// +[AttributeUsage(AttributeTargets.Parameter)] +public class Variable : Attribute { /// - /// Attribute used to decorate a parameter of a block method to indicate it should be initialized - /// as a setting of type variable, optionally with the given default variable name. + /// The default variable name to assign as input to this parameter, e.g. data.SOURCE /// - [AttributeUsage(AttributeTargets.Parameter)] - public class Variable : Attribute - { - /// - /// The default variable name to assign as input to this parameter, e.g. data.SOURCE - /// - public string defaultVariableName = null; + // ReSharper disable once InconsistentNaming + public string? defaultVariableName; - public Variable() - { + /// + /// Marks the parameter as a variable with no default name. + /// + public Variable() + { - } + } - public Variable(string defaultVariableName) - { - this.defaultVariableName = defaultVariableName; - } + /// + /// Marks the parameter as a variable with the given default name. + /// + public Variable(string defaultVariableName) + { + this.defaultVariableName = defaultVariableName; } } diff --git a/RuriLib/Blocks/Captchas/Methods.cs b/RuriLib/Blocks/Captchas/Methods.cs index 5311cdfc4..1802dd55c 100644 --- a/RuriLib/Blocks/Captchas/Methods.cs +++ b/RuriLib/Blocks/Captchas/Methods.cs @@ -11,9 +11,15 @@ namespace RuriLib.Blocks.Captchas; +/// +/// Methods for solving captchas. +/// [BlockCategory("Captchas", "Blocks for solving captchas", "#7df9ff")] public static class Methods { + /// + /// Solves a text captcha. + /// [Block("Solves a text captcha")] public static async Task SolveTextCaptcha(BotData data, [BlockParam("Question", "The description of the captcha to solve, e.g. What is 2+2?")] string question, @@ -35,6 +41,9 @@ public static async Task SolveTextCaptcha(BotData data, return response.Response; } + /// + /// Solves an image captcha. + /// [Block("Solves an image captcha")] public static async Task SolveImageCaptcha(BotData data, string base64, CaptchaLanguageGroup languageGroup = CaptchaLanguageGroup.NotSpecified, @@ -64,6 +73,9 @@ public static async Task SolveImageCaptcha(BotData data, string base64, return response.Response; } + /// + /// Solves a ReCaptcha v2. + /// [Block("Solves a ReCaptcha v2")] public static async Task SolveRecaptchaV2(BotData data, string siteKey, string siteUrl, string sData = "", bool enterprise = false, bool isInvisible = false, bool useProxy = false, @@ -80,11 +92,16 @@ public static async Task SolveRecaptchaV2(BotData data, string siteKey, return response.Response; } - // For backwards compatibility + /// + /// Solves a ReCaptcha v2 (backwards compatibility). + /// public static Task SolveRecaptchaV2(BotData data, string siteKey, string siteUrl, bool isInvisible = false, bool useProxy = false, string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36") => SolveRecaptchaV2(data, siteKey, siteUrl, "", false, isInvisible, useProxy, userAgent); + /// + /// Solves a ReCaptcha v3. + /// [Block("Solves a ReCaptcha v3")] public static async Task SolveRecaptchaV3(BotData data, string siteKey, string siteUrl, string action, float minScore = 0.3F, bool enterprise = false, bool useProxy = false, @@ -101,11 +118,16 @@ public static async Task SolveRecaptchaV3(BotData data, string siteKey, return response.Response; } - // For backwards compatibility + /// + /// Solves a ReCaptcha v3 (backwards compatibility). + /// public static Task SolveRecaptchaV3(BotData data, string siteKey, string siteUrl, string action, float minScore = 0.3F, bool useProxy = false, string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36") => SolveRecaptchaV3(data, siteKey, siteUrl, action, minScore, false, useProxy, userAgent); + /// + /// Solves a FunCaptcha. + /// [Block("Solves a FunCaptcha")] public static async Task SolveFunCaptcha(BotData data, string publicKey, string serviceUrl, string siteUrl, bool noJS = false, string? extraData = null, bool useProxy = false, @@ -122,12 +144,17 @@ public static async Task SolveFunCaptcha(BotData data, string publicKey, return response.Response; } - // For backwards compatibility + /// + /// Solves a FunCaptcha (backwards compatibility). + /// public static async Task SolveFunCaptcha(BotData data, string publicKey, string serviceUrl, string siteUrl, bool noJS = false, bool useProxy = false, string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36") => await SolveFunCaptcha(data, publicKey, serviceUrl, siteUrl, noJS, extraData: null, useProxy, userAgent); + /// + /// Solves a HCaptcha. + /// [Block("Solves a HCaptcha")] public static async Task SolveHCaptcha(BotData data, string siteKey, string siteUrl, string? enterprisePayload = null, bool isInvisible = false, bool useProxy = false, @@ -144,12 +171,17 @@ public static async Task SolveHCaptcha(BotData data, string siteKey, str return response.Response; } - // For backwards compatibility + /// + /// Solves a HCaptcha (backwards compatibility). + /// public static async Task SolveHCaptcha(BotData data, string siteKey, string siteUrl, bool useProxy = false, string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36") => await SolveHCaptcha(data, siteKey, siteUrl, enterprisePayload: null, isInvisible: false, useProxy, userAgent); + /// + /// Solves a KeyCaptcha. + /// [Block("Solves a KeyCaptcha")] public static async Task SolveKeyCaptcha(BotData data, string userId, string sessionId, string webServerSign1, string webServerSign2, string siteUrl, bool useProxy = false, @@ -166,6 +198,9 @@ public static async Task SolveKeyCaptcha(BotData data, string userId, st return response.Response; } + /// + /// Solves a GeeTest v3 captcha. + /// [Block("Solves a GeeTest v3 captcha", extraInfo = "The response will be a list and its elements are (in order) challenge, validate, seccode")] public static async Task> SolveGeeTestCaptcha(BotData data, string gt, string apiChallenge, @@ -186,6 +221,9 @@ public static async Task> SolveGeeTestCaptcha(BotData data, string return [response.Challenge, response.Validate, response.SecCode]; } + /// + /// Solves a Capy captcha. + /// [Block("Solves a Capy captcha")] public static async Task> SolveCapyCaptcha(BotData data, string siteKey, string siteUrl, bool useProxy = false, @@ -205,6 +243,9 @@ public static async Task> SolveCapyCaptcha(BotData data, string sit return [response.ChallengeKey, response.CaptchaKey, response.Answer]; } + /// + /// Solves a DataDome captcha. + /// [Block("Solves a DataDome captcha")] public static async Task SolveDataDomeCaptcha(BotData data, string captchaUrl, string siteUrl, bool useProxy = false, @@ -221,6 +262,9 @@ public static async Task SolveDataDomeCaptcha(BotData data, string captc return response.Response; } + /// + /// Solves a Cloudflare Turnstile captcha. + /// [Block("Solves a Cloudflare Turnstile captcha")] public static async Task SolveCloudflareTurnstileCaptcha(BotData data, string siteKey, string siteUrl, string? action = null, string? cData = null, string? pageData = null, bool useProxy = false, @@ -238,6 +282,9 @@ public static async Task SolveCloudflareTurnstileCaptcha(BotData data, s return response.Response; } + /// + /// Solves a Lemin Cropped captcha. + /// [Block("Solves a Lemin Cropped captcha", extraInfo = "The response will be a list and its elements are (in order) answer, challenge ID")] public static async Task> SolveLeminCroppedCaptcha(BotData data, string captchaId, string siteUrl, @@ -258,6 +305,9 @@ public static async Task> SolveLeminCroppedCaptcha(BotData data, st return [response.Answer, response.ChallengeId]; } + /// + /// Solves an Amazon WAF captcha. + /// [Block("Solves an Amazon WAF captcha")] public static async Task SolveAmazonWafCaptcha(BotData data, string siteKey, string siteUrl, string iv, string context, string? challengeScript = null, string? captchaScript = null, bool useProxy = false, @@ -275,6 +325,9 @@ public static async Task SolveAmazonWafCaptcha(BotData data, string site return response.Response; } + /// + /// Solves a Cyber SiARA captcha. + /// [Block("Solves a Cyber SiARA captcha")] public static async Task SolveCyberSiAraCaptcha(BotData data, string masterUrlId, string siteUrl, bool useProxy = false, @@ -292,6 +345,9 @@ public static async Task SolveCyberSiAraCaptcha(BotData data, string mas return response.Response; } + /// + /// Solves a MT captcha. + /// [Block("Solves an MT captcha")] public static async Task SolveMtCaptcha(BotData data, string siteKey, string siteUrl, bool useProxy = false, string userAgent = @@ -308,6 +364,9 @@ public static async Task SolveMtCaptcha(BotData data, string siteKey, st return response.Response; } + /// + /// Solves a CapMonster captcha. + /// [Block("Solves a CutCaptcha")] public static async Task SolveCutCaptcha(BotData data, string miseryKey, string apiKey, string siteUrl, bool useProxy = false, string userAgent = @@ -324,6 +383,9 @@ public static async Task SolveCutCaptcha(BotData data, string miseryKey, return response.Response; } + /// + /// Solves a Friendly captcha. + /// [Block("Solves a Friendly captcha")] public static async Task SolveFriendlyCaptcha(BotData data, string siteKey, string siteUrl, bool useProxy = false, string userAgent = @@ -340,6 +402,9 @@ public static async Task SolveFriendlyCaptcha(BotData data, string siteK return response.Response; } + /// + /// Solves an ATB captcha. + /// [Block("Solves an atb captcha")] public static async Task SolveAtbCaptcha(BotData data, string appId, string apiServer, string siteUrl, bool useProxy = false, string userAgent = @@ -356,6 +421,9 @@ public static async Task SolveAtbCaptcha(BotData data, string appId, str return response.Response; } + /// + /// Solves a Tencent captcha. + /// [Block("Solves a Tencent captcha", extraInfo = "The response will be a list and its elements are (in order) app id, ticket, return code, random string")] public static async Task> SolveTencentCaptcha(BotData data, string appId, string siteUrl, @@ -377,6 +445,9 @@ public static async Task> SolveTencentCaptcha(BotData data, string return [response.AppId, response.Ticket, response.ReturnCode.ToString(), response.RandomString]; } + /// + /// Solves an audio captcha. + /// [Block("Solves an audio captcha")] public static async Task SolveAudioCaptcha(BotData data, string base64, CaptchaLanguage language = CaptchaLanguage.NotSpecified) @@ -394,6 +465,9 @@ public static async Task SolveAudioCaptcha(BotData data, string base64, return response.Response; } + /// + /// Solves a ReCaptcha Mobile. + /// [Block("Solves a ReCaptcha Mobile")] public static async Task SolveRecaptchaMobile(BotData data, string appPackageName, string appKey, string appAction, bool useProxy = false, string userAgent = @@ -410,6 +484,9 @@ public static async Task SolveRecaptchaMobile(BotData data, string appPa return response.Response; } + /// + /// Solves a GeeTest v4 captcha. + /// [Block("Solves a GeeTest v4 captcha", extraInfo = "The response will be a list and its elements are (in order) captcha id, lot number, pass token, gen time, captcha output")] @@ -433,6 +510,9 @@ public static async Task> SolveGeeTestV4Captcha(BotData data, strin return [response.CaptchaId, response.LotNumber, response.PassToken, response.GenTime, response.CaptchaOutput]; } + /// + /// Solves a Cloudflare Challenge page. + /// [Block("Solves a Cloudflare Challenge page", extraInfo = "The response will contain the value of the cf_clearance cookie")] public static async Task SolveCloudflareChallengePage(BotData data, @@ -451,13 +531,16 @@ public static async Task SolveCloudflareChallengePage(BotData data, return response.Response; } + /// + /// Reports an incorrectly solved captcha. + /// [Block("Reports an incorrectly solved captcha to the service in order to get funds back")] public static async Task ReportLastSolution(BotData data) { - var lastCaptcha = data.TryGetObject("lastCaptchaInfo"); - data.Logger.LogHeader(); + var lastCaptcha = data.TryGetObject("lastCaptchaInfo"); + if (lastCaptcha is null) { data.Logger.Log("No captcha has been solved yet", LogColors.ElectricBlue); diff --git a/RuriLib/Blocks/Conditions/Methods.cs b/RuriLib/Blocks/Conditions/Methods.cs index b425fe95a..7fc89cdf1 100644 --- a/RuriLib/Blocks/Conditions/Methods.cs +++ b/RuriLib/Blocks/Conditions/Methods.cs @@ -4,118 +4,152 @@ using RuriLib.Models.Conditions.Comparisons; using System.Collections.Generic; -namespace RuriLib.Blocks.Conditions +namespace RuriLib.Blocks.Conditions; + +/// +/// Blocks to check conditions. +/// +[BlockCategory("Conditions", "Blocks that have to do with checking conditions", "#1e90ff")] +public static class Methods { - [BlockCategory("Conditions", "Blocks that have to do with checking conditions", "#1e90ff")] - public static class Methods + /* + * These are not blocks, but they take BotData as an input. The KeycheckBlockInstance will take care + * of writing C# code that calls these methods where necessary once it's transpiled. + */ + + /// + /// Checks a condition with two boolean terms. + /// + public static bool CheckCondition(BotData data, bool leftTerm, BoolComparison comparison, bool rightTerm) { - /* - * These are not blocks, but they take BotData as an input. The KeycheckBlockInstance will take care - * of writing C# code that calls these methods where necessary once it's transpiled. - */ - - public static bool CheckCondition(BotData data, bool leftTerm, BoolComparison comparison, bool rightTerm) - { - var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); + data.Logger.LogHeader(); + + var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Matched key {leftTerm} {comparison} {rightTerm}"); - } - - return result; + if (result) + { + data.Logger.Log($"Matched key {leftTerm} {comparison} {rightTerm}"); } - public static bool CheckCondition(BotData data, string leftTerm, StrComparison comparison, string rightTerm) - { - var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); + return result; + } - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Matched key {leftTerm.TruncatePretty(50)} {comparison} {rightTerm.TruncatePretty(50)}"); - } + /// + /// Checks a condition with two string terms. + /// + public static bool CheckCondition(BotData data, string leftTerm, StrComparison comparison, string rightTerm) + { + data.Logger.LogHeader(); + + var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); - return result; + if (result) + { + data.Logger.Log($"Matched key {leftTerm.TruncatePretty(50)} {comparison} {rightTerm.TruncatePretty(50)}"); } - public static bool CheckCondition(BotData data, List leftTerm, ListComparison comparison, string rightTerm) - { - var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); + return result; + } - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Matched key {leftTerm.AsString().TruncatePretty(50)} {comparison} {rightTerm.TruncatePretty(50)}"); - } + /// + /// Checks a condition between a list of strings and a string. + /// + public static bool CheckCondition(BotData data, List leftTerm, ListComparison comparison, string rightTerm) + { + data.Logger.LogHeader(); + + var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); - return result; + if (result) + { + data.Logger.Log($"Matched key {leftTerm.AsString().TruncatePretty(50)} {comparison} {rightTerm.TruncatePretty(50)}"); } - public static bool CheckCondition(BotData data, int leftTerm, NumComparison comparison, int rightTerm) - { - var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); + return result; + } - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Matched key {leftTerm} {comparison} {rightTerm}"); - } + /// + /// Checks a condition with two integer terms. + /// + public static bool CheckCondition(BotData data, int leftTerm, NumComparison comparison, int rightTerm) + { + data.Logger.LogHeader(); + + var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); - return result; + if (result) + { + data.Logger.Log($"Matched key {leftTerm} {comparison} {rightTerm}"); } - public static bool CheckCondition(BotData data, float leftTerm, NumComparison comparison, float rightTerm) - { - var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); + return result; + } - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Matched key {leftTerm} {comparison} {rightTerm}"); - } + /// + /// Checks a condition with two float terms. + /// + public static bool CheckCondition(BotData data, float leftTerm, NumComparison comparison, float rightTerm) + { + data.Logger.LogHeader(); + + var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); - return result; + if (result) + { + data.Logger.Log($"Matched key {leftTerm} {comparison} {rightTerm}"); } - public static bool CheckCondition(BotData data, Dictionary leftTerm, DictComparison comparison, string rightTerm) - { - var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); + return result; + } - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Matched key {leftTerm.AsString().TruncatePretty(50)} {comparison} {rightTerm.TruncatePretty(50)}"); - } + /// + /// Checks a condition between a dictionary of strings and a string. + /// + public static bool CheckCondition(BotData data, Dictionary leftTerm, DictComparison comparison, string rightTerm) + { + data.Logger.LogHeader(); + + var result = RuriLib.Functions.Conditions.Conditions.Check(leftTerm, comparison, rightTerm); - return result; + if (result) + { + data.Logger.Log($"Matched key {leftTerm.AsString().TruncatePretty(50)} {comparison} {rightTerm.TruncatePretty(50)}"); } - public static bool CheckGlobalBanKeys(BotData data) - { - var result = data.Providers.ProxySettings.ContainsBanKey(data.SOURCE, out var matchedKey); - - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Found global ban key: {matchedKey}"); - } + return result; + } - return result; + /// + /// Checks if the source contains a global ban key. + /// + public static bool CheckGlobalBanKeys(BotData data) + { + data.Logger.LogHeader(); + + var result = data.Providers.ProxySettings.ContainsBanKey(data.SOURCE, out var matchedKey); + + if (result) + { + data.Logger.Log($"Found global ban key: {matchedKey}"); } - public static bool CheckGlobalRetryKeys(BotData data) - { - var result = data.Providers.ProxySettings.ContainsRetryKey(data.SOURCE, out var matchedKey); + return result; + } - if (result) - { - data.Logger.LogHeader(); - data.Logger.Log($"Found global retry key: {matchedKey}"); - } + /// + /// Checks if the source contains a global retry key. + /// + public static bool CheckGlobalRetryKeys(BotData data) + { + data.Logger.LogHeader(); + + var result = data.Providers.ProxySettings.ContainsRetryKey(data.SOURCE, out var matchedKey); - return result; + if (result) + { + data.Logger.Log($"Found global retry key: {matchedKey}"); } + + return result; } } diff --git a/RuriLib/Blocks/Functions/ByteArrayFunctions/Methods.cs b/RuriLib/Blocks/Functions/ByteArrayFunctions/Methods.cs index 60f2463ce..157b46e80 100644 --- a/RuriLib/Blocks/Functions/ByteArrayFunctions/Methods.cs +++ b/RuriLib/Blocks/Functions/ByteArrayFunctions/Methods.cs @@ -4,21 +4,28 @@ using RuriLib.Models.Bots; using System; -namespace RuriLib.Blocks.Functions.ByteArray +// ReSharper disable once CheckNamespace +namespace RuriLib.Blocks.Functions.ByteArray; + +/// +/// Blocks for working with byte arrays. +/// +[BlockCategory("Byte Array Functions", "Blocks for working with byte arrays", "#9acd32")] +public static class Methods { - [BlockCategory("Byte Array Functions", "Blocks for working with byte arrays", "#9acd32")] - public static class Methods + /// + /// Merge two byte arrays together to form a longer one. + /// + [Block("Merges two byte arrays together to form a longer one")] + public static byte[] MergeByteArrays(BotData data, byte[] first, byte[] second) { - [Block("Merges two byte arrays together to form a longer one")] - public static byte[] MergeByteArrays(BotData data, byte[] first, byte[] second) - { - var merged = new byte[first.Length + second.Length]; - Buffer.BlockCopy(first, 0, merged, 0, first.Length); - Buffer.BlockCopy(second, 0, merged, second.Length, second.Length); + data.Logger.LogHeader(); + + var merged = new byte[first.Length + second.Length]; + Buffer.BlockCopy(first, 0, merged, 0, first.Length); + Buffer.BlockCopy(second, 0, merged, second.Length, second.Length); - data.Logger.LogHeader(); - data.Logger.Log($"Merged {HexConverter.ToHexString(first)} and {HexConverter.ToHexString(second)} into {HexConverter.ToHexString(merged)}", LogColors.YellowGreen); - return merged; - } + data.Logger.Log($"Merged {HexConverter.ToHexString(first)} and {HexConverter.ToHexString(second)} into {HexConverter.ToHexString(merged)}", LogColors.YellowGreen); + return merged; } } diff --git a/RuriLib/Blocks/Functions/Constants/Methods.cs b/RuriLib/Blocks/Functions/Constants/Methods.cs index 41cc0aa6e..b6b59b935 100644 --- a/RuriLib/Blocks/Functions/Constants/Methods.cs +++ b/RuriLib/Blocks/Functions/Constants/Methods.cs @@ -6,65 +6,95 @@ using System.Globalization; using System.Linq; -namespace RuriLib.Blocks.Functions.Constants +namespace RuriLib.Blocks.Functions.Constants; + +/// +/// Blocks to assign constant values to variables. +/// +[BlockCategory("Constants", "Blocks that allow to assign constant values to variables", "#9acd32")] +public static class Methods { - [BlockCategory("Constants", "Blocks that allow to assign constant values to variables", "#9acd32")] - public static class Methods + /// + /// Creates a constant string. + /// + [Block("Creates a constant string")] + public static string ConstantString(BotData data, [MultiLine] string value) { - [Block("Creates a constant string")] - public static string ConstantString(BotData data, [MultiLine] string value) - { - data.Logger.LogHeader(); - data.Logger.Log($"Set constant value {value}", LogColors.YellowGreen); - return value; - } + data.Logger.LogHeader(); + + data.Logger.Log($"Set constant value {value}", LogColors.YellowGreen); + return value; + } - [Block("Creates a constant integer")] - public static int ConstantInteger(BotData data, int value) - { - data.Logger.LogHeader(); - data.Logger.Log($"Set constant value {value}", LogColors.YellowGreen); - return value; - } + /// + /// Creates a constant integer. + /// + [Block("Creates a constant integer")] + public static int ConstantInteger(BotData data, int value) + { + data.Logger.LogHeader(); + + data.Logger.Log($"Set constant value {value}", LogColors.YellowGreen); + return value; + } - [Block("Creates a constant float")] - public static float ConstantFloat(BotData data, float value) - { - data.Logger.LogHeader(); - data.Logger.Log($"Set constant value {value.ToString(CultureInfo.InvariantCulture)}", LogColors.YellowGreen); - return value; - } + /// + /// Creates a constant float. + /// + [Block("Creates a constant float")] + public static float ConstantFloat(BotData data, float value) + { + data.Logger.LogHeader(); + + data.Logger.Log($"Set constant value {value.ToString(CultureInfo.InvariantCulture)}", LogColors.YellowGreen); + return value; + } - [Block("Creates a constant bool")] - public static bool ConstantBool(BotData data, bool value) - { - data.Logger.LogHeader(); - data.Logger.Log($"Set constant value {value}", LogColors.YellowGreen); - return value; - } + /// + /// Creates a constant bool. + /// + [Block("Creates a constant bool")] + public static bool ConstantBool(BotData data, bool value) + { + data.Logger.LogHeader(); + + data.Logger.Log($"Set constant value {value}", LogColors.YellowGreen); + return value; + } - [Block("Creates a constant byte array")] - public static byte[] ConstantByteArray(BotData data, byte[] value) - { - data.Logger.LogHeader(); - data.Logger.Log($"Set constant value {HexConverter.ToHexString(value)}", LogColors.YellowGreen); - return value; - } + /// + /// Creates a constant byte array. + /// + [Block("Creates a constant byte array")] + public static byte[] ConstantByteArray(BotData data, byte[] value) + { + data.Logger.LogHeader(); + + data.Logger.Log($"Set constant value {HexConverter.ToHexString(value)}", LogColors.YellowGreen); + return value; + } - [Block("Creates a constant list")] - public static List ConstantList(BotData data, List value) - { - data.Logger.LogHeader(); - data.Logger.Log($"Set constant value {string.Join(", ", value)}", LogColors.YellowGreen); - return value.Select(i => i).ToList(); // Clone the list - } + /// + /// Creates a constant list. + /// + [Block("Creates a constant list")] + public static List ConstantList(BotData data, List value) + { + data.Logger.LogHeader(); + + data.Logger.Log($"Set constant value {string.Join(", ", value)}", LogColors.YellowGreen); + return value.Select(i => i).ToList(); // Clone the list + } - [Block("Creates a constant dictionary")] - public static Dictionary ConstantDictionary(BotData data, Dictionary value) - { - data.Logger.LogHeader(); - data.Logger.Log($"Set constant value {string.Join(", ", value.Select(kvp => $"({kvp.Key}, {kvp.Value})"))}", LogColors.YellowGreen); - return value.Select(i => i).ToDictionary(i => i.Key, i => i.Value); - } + /// + /// Creates a constant dictionary. + /// + [Block("Creates a constant dictionary")] + public static Dictionary ConstantDictionary(BotData data, Dictionary value) + { + data.Logger.LogHeader(); + + data.Logger.Log($"Set constant value {string.Join(", ", value.Select(kvp => $"({kvp.Key}, {kvp.Value})"))}", LogColors.YellowGreen); + return value.Select(i => i).ToDictionary(i => i.Key, i => i.Value); } } diff --git a/RuriLib/Blocks/Functions/Crypto/Methods.cs b/RuriLib/Blocks/Functions/Crypto/Methods.cs index b51e529a0..99327c155 100644 --- a/RuriLib/Blocks/Functions/Crypto/Methods.cs +++ b/RuriLib/Blocks/Functions/Crypto/Methods.cs @@ -12,270 +12,356 @@ using System.Security.Cryptography; using System.Text; -namespace RuriLib.Blocks.Functions.Crypto +namespace RuriLib.Blocks.Functions.Crypto; + +/// +/// Blocks for executing cryptographic functions. +/// +[BlockCategory("Crypto", "Blocks for executing cryptographic functions", "#9acd32")] +public static class Methods { - [BlockCategory("Crypto", "Blocks for executing cryptographic functions", "#9acd32")] - public static class Methods + /// + /// XOR encryption and decryption on byte arrays. + /// + [Block("XOR En-/Decryption on byte arrays", name = "XOR")] + public static byte[] XOR(BotData data, byte[] bytes, byte[] key) { - [Block("XOR En-/Decryption on byte arrays", name = "XOR")] - public static byte[] XOR(BotData data, byte[] bytes, byte[] key) - { - var xored = RuriLib.Functions.Crypto.Crypto.XOR(bytes, key); - data.Logger.LogHeader(); - data.Logger.Log($"XORed the two byte arrays and got {HexConverter.ToHexString(xored)}", LogColors.YellowGreen); - return xored; - } + data.Logger.LogHeader(); + + var xor = RuriLib.Functions.Crypto.Crypto.XOR(bytes, key); + data.Logger.Log($"XORed the two byte arrays and got {HexConverter.ToHexString(xor)}", LogColors.YellowGreen); + return xor; + } - [Block("Does a simple XOR En-/Decryption on strings", name = "XOR Strings")] - public static string XORStrings(BotData data, string text, string key) - { - var xored = RuriLib.Functions.Crypto.Crypto.XORStrings(text, key); - data.Logger.LogHeader(); - data.Logger.Log($"XORed: {text} with {key} with the outcome {xored}", LogColors.YellowGreen); - return xored; - } + /// + /// XOR encryption and decryption on strings. + /// + [Block("Does a simple XOR En-/Decryption on strings", name = "XOR Strings")] + public static string XORStrings(BotData data, string text, string key) + { + data.Logger.LogHeader(); + + var xor = RuriLib.Functions.Crypto.Crypto.XORStrings(text, key); + data.Logger.Log($"XORed: {text} with {key} with the outcome {xor}", LogColors.YellowGreen); + return xor; + } - [Block("Hashes data using the specified hashing function")] - public static byte[] Hash(BotData data, byte[] input, HashFunction hashFunction = HashFunction.MD5) - { - var hashed = Hash(input, hashFunction); - data.Logger.LogHeader(); - data.Logger.Log($"Computed hash: {HexConverter.ToHexString(hashed)}", LogColors.YellowGreen); - return hashed; - } + /// + /// Hashes data using the specified hashing function. + /// + [Block("Hashes data using the specified hashing function")] + public static byte[] Hash(BotData data, byte[] input, HashFunction hashFunction = HashFunction.MD5) + { + data.Logger.LogHeader(); + + var hashed = Hash(input, hashFunction); + data.Logger.Log($"Computed hash: {HexConverter.ToHexString(hashed)}", LogColors.YellowGreen); + return hashed; + } - [Block("Hashes a UTF8 string to a HEX-encoded lowercase string using the specified hashing function")] - public static string HashString(BotData data, string input, HashFunction hashFunction = HashFunction.MD5) - { - var hashed = HexConverter.ToHexString(Hash(Encoding.UTF8.GetBytes(input), hashFunction)); - data.Logger.LogHeader(); - data.Logger.Log($"Computed hash: {hashed}", LogColors.YellowGreen); - return hashed; - } + /// + /// Hashes a UTF8 string to a HEX-encoded lowercase string using the specified hashing function. + /// + [Block("Hashes a UTF8 string to a HEX-encoded lowercase string using the specified hashing function")] + public static string HashString(BotData data, string input, HashFunction hashFunction = HashFunction.MD5) + { + data.Logger.LogHeader(); + + var hashed = HexConverter.ToHexString(Hash(Encoding.UTF8.GetBytes(input), hashFunction)); + data.Logger.Log($"Computed hash: {hashed}", LogColors.YellowGreen); + return hashed; + } - [Block("Hashes a string using NTLM", name = "NTLM Hash")] - public static byte[] NTLMHash(BotData data, string input) - { - var hashed = RuriLib.Functions.Crypto.Crypto.NTLM(input); - data.Logger.LogHeader(); - data.Logger.Log($"Computed hash: {HexConverter.ToHexString(hashed)}", LogColors.YellowGreen); - return hashed; - } + /// + /// Hashes a string using NTLM. + /// + [Block("Hashes a string using NTLM", name = "NTLM Hash")] + public static byte[] NTLMHash(BotData data, string input) + { + data.Logger.LogHeader(); + + var hashed = RuriLib.Functions.Crypto.Crypto.NTLM(input); + data.Logger.Log($"Computed hash: {HexConverter.ToHexString(hashed)}", LogColors.YellowGreen); + return hashed; + } - [Block("Computes the HMAC signature of some data using the specified secret key and hashing function")] - public static byte[] Hmac(BotData data, byte[] input, byte[] key, HashFunction hashFunction = HashFunction.MD5) - { - var hmac = Hmac(input, key, hashFunction); - data.Logger.LogHeader(); - data.Logger.Log($"Computed HMAC: {HexConverter.ToHexString(hmac)}", LogColors.YellowGreen); - return hmac; - } + /// + /// Computes the HMAC signature of some data using the specified secret key and hashing function. + /// + [Block("Computes the HMAC signature of some data using the specified secret key and hashing function")] + public static byte[] Hmac(BotData data, byte[] input, byte[] key, HashFunction hashFunction = HashFunction.MD5) + { + data.Logger.LogHeader(); + + var hmac = Hmac(input, key, hashFunction); + data.Logger.Log($"Computed HMAC: {HexConverter.ToHexString(hmac)}", LogColors.YellowGreen); + return hmac; + } - [Block("Computes the HMAC signature as a HEX-encoded lowercase string from a given UTF8 string using the specified key and hashing function")] - public static string HmacString(BotData data, string input, byte[] key, HashFunction hashFunction = HashFunction.MD5) + /// + /// Computes the HMAC signature as a HEX-encoded lowercase string from a given UTF8 string using the specified key and hashing function. + /// + [Block("Computes the HMAC signature as a HEX-encoded lowercase string from a given UTF8 string using the specified key and hashing function")] + public static string HmacString(BotData data, string input, byte[] key, HashFunction hashFunction = HashFunction.MD5) + { + data.Logger.LogHeader(); + + var hmac = HexConverter.ToHexString(Hmac(Encoding.UTF8.GetBytes(input), key, hashFunction)); + data.Logger.Log($"Computed HMAC: {hmac}", LogColors.YellowGreen); + return hmac; + } + + private static byte[] Hash(byte[] input, HashFunction function) + { + return function switch { - var hmac = HexConverter.ToHexString(Hmac(Encoding.UTF8.GetBytes(input), key, hashFunction)); - data.Logger.LogHeader(); - data.Logger.Log($"Computed HMAC: {hmac}", LogColors.YellowGreen); - return hmac; - } + HashFunction.MD4 => RuriLib.Functions.Crypto.Crypto.MD4(input), + HashFunction.MD5 => RuriLib.Functions.Crypto.Crypto.MD5(input), + HashFunction.SHA1 => RuriLib.Functions.Crypto.Crypto.SHA1(input), + HashFunction.SHA256 => RuriLib.Functions.Crypto.Crypto.SHA256(input), + HashFunction.SHA384 => RuriLib.Functions.Crypto.Crypto.SHA384(input), + HashFunction.SHA512 => RuriLib.Functions.Crypto.Crypto.SHA512(input), + _ => throw new NotSupportedException() + }; + } + + private static byte[] Hmac(string input, byte[] key, HashFunction function) + => Hmac(Encoding.UTF8.GetBytes(input), key, function); - private static byte[] Hash(byte[] input, HashFunction function) + private static byte[] Hmac(byte[] input, byte[] key, HashFunction function) + { + return function switch { - return function switch - { - HashFunction.MD4 => RuriLib.Functions.Crypto.Crypto.MD4(input), - HashFunction.MD5 => RuriLib.Functions.Crypto.Crypto.MD5(input), - HashFunction.SHA1 => RuriLib.Functions.Crypto.Crypto.SHA1(input), - HashFunction.SHA256 => RuriLib.Functions.Crypto.Crypto.SHA256(input), - HashFunction.SHA384 => RuriLib.Functions.Crypto.Crypto.SHA384(input), - HashFunction.SHA512 => RuriLib.Functions.Crypto.Crypto.SHA512(input), - _ => throw new NotSupportedException() - }; - } + HashFunction.MD5 => RuriLib.Functions.Crypto.Crypto.HMACMD5(input, key), + HashFunction.SHA1 => RuriLib.Functions.Crypto.Crypto.HMACSHA1(input, key), + HashFunction.SHA256 => RuriLib.Functions.Crypto.Crypto.HMACSHA256(input, key), + HashFunction.SHA384 => RuriLib.Functions.Crypto.Crypto.HMACSHA384(input, key), + HashFunction.SHA512 => RuriLib.Functions.Crypto.Crypto.HMACSHA512(input, key), + _ => throw new NotSupportedException() + }; + } - private static byte[] Hmac(string input, byte[] key, HashFunction function) => Hmac(Encoding.UTF8.GetBytes(input), key, function); + /// + /// Hashes data using the Scrypt algorithm. + /// + [Block("Hashes data using the Scrypt algorithm")] + public static string ScryptString(BotData data, string password, string salt, int iterationCount = 16384, int blockSize = 8, int threadCount = 1) + { + data.Logger.LogHeader(); + + var rng = new FakeRng(salt); + var encoder = new ScryptEncoder(iterationCount, blockSize, threadCount, rng); + var hashed = encoder.Encode(password); + data.Logger.Log($"Computed Scrypt: {hashed}", LogColors.YellowGreen); + return hashed; + } - private static byte[] Hmac(byte[] input, byte[] key, HashFunction function) - { - return function switch - { - HashFunction.MD5 => RuriLib.Functions.Crypto.Crypto.HMACMD5(input, key), - HashFunction.SHA1 => RuriLib.Functions.Crypto.Crypto.HMACSHA1(input, key), - HashFunction.SHA256 => RuriLib.Functions.Crypto.Crypto.HMACSHA256(input, key), - HashFunction.SHA384 => RuriLib.Functions.Crypto.Crypto.HMACSHA384(input, key), - HashFunction.SHA512 => RuriLib.Functions.Crypto.Crypto.HMACSHA512(input, key), - _ => throw new NotSupportedException() - }; - } + // Used for Scrypt.NET because it doesn't support a parametrized salt + private class FakeRng : RandomNumberGenerator + { + private readonly byte[] _salt; - [Block("Hashes data using the Scrypt algorithm")] - public static string ScryptString(BotData data, string password, string salt, int iterationCount = 16384, int blockSize = 8, int threadCount = 1) + public FakeRng(string salt) { - var rng = new FakeRNG(salt); - var encoder = new ScryptEncoder(iterationCount, blockSize, threadCount, rng); - var hashed = encoder.Encode(password); - data.Logger.LogHeader(); - data.Logger.Log($"Computed Scrypt: {hashed}", LogColors.YellowGreen); - return hashed; + this._salt = Encoding.UTF8.GetBytes(salt); } - // Used for Scrypt.NET because it doesn't support a parametrized salt - private class FakeRNG : RandomNumberGenerator + public override void GetBytes(byte[] data) { - private readonly byte[] salt; - - public FakeRNG(string salt) - { - this.salt = Encoding.UTF8.GetBytes(salt); - } - - public override void GetBytes(byte[] data) + for (var i = 0; i < _salt.Length; i++) { - for (var i = 0; i < salt.Length; i++) - { - data[i] = salt[i]; - } + data[i] = _salt[i]; } } + } - [Block("Encrypts data using RSA", name = "RSA Encrypt")] - public static byte[] RSAEncrypt(BotData data, byte[] plainText, byte[] modulus, byte[] exponent, bool useOAEP) - { - var cipherText = RuriLib.Functions.Crypto.Crypto.RSAEncrypt(plainText, modulus, exponent, useOAEP); - data.Logger.LogHeader(); - data.Logger.Log($"Encrypted: {HexConverter.ToHexString(cipherText)}", LogColors.YellowGreen); - return cipherText; - } - - [Block("Decrypts data using RSA", name = "RSA Decrypt")] - public static byte[] RSADecrypt(BotData data, byte[] cipherText, byte[] modulus, byte[] d, bool useOAEP) - { - var plainText = RuriLib.Functions.Crypto.Crypto.RSADecrypt(cipherText, modulus, d, useOAEP); - data.Logger.LogHeader(); - data.Logger.Log($"Decrypted: {HexConverter.ToHexString(plainText)}", LogColors.YellowGreen); - return plainText; - } + /// + /// Encrypts data using RSA. + /// + [Block("Encrypts data using RSA", name = "RSA Encrypt")] + public static byte[] RSAEncrypt(BotData data, byte[] plainText, byte[] modulus, byte[] exponent, bool useOAEP) + { + data.Logger.LogHeader(); + + var cipherText = RuriLib.Functions.Crypto.Crypto.RSAEncrypt(plainText, modulus, exponent, useOAEP); + data.Logger.Log($"Encrypted: {HexConverter.ToHexString(cipherText)}", LogColors.YellowGreen); + return cipherText; + } - [Block("Encrypts data using RSA with PKCS1PAD2", name = "RSA PKCS1PAD2")] - public static byte[] RSAPkcs1Pad2(BotData data, string plainText, string hexModulus, string hexExponent) - { - var encrypted = RuriLib.Functions.Crypto.Crypto.RSAPkcs1Pad2(plainText, hexModulus, hexExponent); - data.Logger.LogHeader(); - data.Logger.Log($"Encrypted: {HexConverter.ToHexString(encrypted)}", LogColors.YellowGreen); - return encrypted; - } + /// + /// Decrypts data using RSA. + /// + [Block("Decrypts data using RSA", name = "RSA Decrypt")] + public static byte[] RSADecrypt(BotData data, byte[] cipherText, byte[] modulus, byte[] d, bool useOAEP) + { + data.Logger.LogHeader(); + + var plainText = RuriLib.Functions.Crypto.Crypto.RSADecrypt(cipherText, modulus, d, useOAEP); + data.Logger.Log($"Decrypted: {HexConverter.ToHexString(plainText)}", LogColors.YellowGreen); + return plainText; + } - [Block("Generates a PKCS v5 #2.0 key using a Password-Based Key Derivation Function", name = "PBKDF2PKCS5")] - public static byte[] PBKDF2PKCS5(BotData data, byte[] password, byte[] salt = null, int saltSize = 8, - int iterations = 1, int keyLength = 16, HashFunction type = HashFunction.SHA1) - { - var derived = RuriLib.Functions.Crypto.Crypto.PBKDF2PKCS5(password, salt, saltSize, iterations, keyLength, type); - data.Logger.LogHeader(); - data.Logger.Log($"Derived: {HexConverter.ToHexString(derived)}", LogColors.YellowGreen); - return derived; - } + /// + /// Encrypts data using RSA with PKCS1PAD2. + /// + [Block("Encrypts data using RSA with PKCS1PAD2", name = "RSA PKCS1PAD2")] + public static byte[] RSAPkcs1Pad2(BotData data, string plainText, string hexModulus, string hexExponent) + { + data.Logger.LogHeader(); + + var encrypted = RuriLib.Functions.Crypto.Crypto.RSAPkcs1Pad2(plainText, hexModulus, hexExponent); + data.Logger.Log($"Encrypted: {HexConverter.ToHexString(encrypted)}", LogColors.YellowGreen); + return encrypted; + } - [Block("Encrypts data with AES", name = "AES Encrypt")] - public static byte[] AESEncrypt(BotData data, byte[] plainText, byte[] key, byte[] iv, - CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) - { - var cipherText = RuriLib.Functions.Crypto.Crypto.AESEncrypt(plainText, key, iv, mode, padding, keySize); - data.Logger.LogHeader(); - data.Logger.Log($"Encrypted: {HexConverter.ToHexString(cipherText)}", LogColors.YellowGreen); - return cipherText; - } + /// + /// Generates a PKCS v5 #2.0 key using a Password-Based Key Derivation Function. + /// + [Block("Generates a PKCS v5 #2.0 key using a Password-Based Key Derivation Function", name = "PBKDF2PKCS5")] + public static byte[] PBKDF2PKCS5(BotData data, byte[] password, byte[] salt = null, int saltSize = 8, + int iterations = 1, int keyLength = 16, HashFunction type = HashFunction.SHA1) + { + data.Logger.LogHeader(); + + var derived = RuriLib.Functions.Crypto.Crypto.PBKDF2PKCS5(password, salt, saltSize, iterations, keyLength, type); + data.Logger.Log($"Derived: {HexConverter.ToHexString(derived)}", LogColors.YellowGreen); + return derived; + } - [Block("Encrypts a string with AES", name = "AES Encrypt String")] - public static byte[] AESEncryptString(BotData data, string plainText, byte[] key, byte[] iv, - CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) - { - var cipherText = RuriLib.Functions.Crypto.Crypto.AESEncryptString(plainText, key, iv, mode, padding, keySize); - data.Logger.LogHeader(); - data.Logger.Log($"Encrypted: {HexConverter.ToHexString(cipherText)}", LogColors.YellowGreen); - return cipherText; - } + /// + /// Encrypts data using AES. + /// + [Block("Encrypts data with AES", name = "AES Encrypt")] + public static byte[] AESEncrypt(BotData data, byte[] plainText, byte[] key, byte[] iv, + CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) + { + data.Logger.LogHeader(); + + var cipherText = RuriLib.Functions.Crypto.Crypto.AESEncrypt(plainText, key, iv, mode, padding, keySize); + data.Logger.Log($"Encrypted: {HexConverter.ToHexString(cipherText)}", LogColors.YellowGreen); + return cipherText; + } + + /// + /// Encrypts a string with AES. + /// + [Block("Encrypts a string with AES", name = "AES Encrypt String")] + public static byte[] AESEncryptString(BotData data, string plainText, byte[] key, byte[] iv, + CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) + { + data.Logger.LogHeader(); + + var cipherText = RuriLib.Functions.Crypto.Crypto.AESEncryptString(plainText, key, iv, mode, padding, keySize); + data.Logger.Log($"Encrypted: {HexConverter.ToHexString(cipherText)}", LogColors.YellowGreen); + return cipherText; + } - [Block("Decrypts data with AES", name = "AES Decrypt")] - public static byte[] AESDecrypt(BotData data, byte[] cipherText, byte[] key, byte[] iv, - CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) - { - var plainText = RuriLib.Functions.Crypto.Crypto.AESDecrypt(cipherText, key, iv, mode, padding, keySize); - data.Logger.LogHeader(); - data.Logger.Log($"Decrypted: {HexConverter.ToHexString(plainText)}", LogColors.YellowGreen); - return plainText; - } + /// + /// Decrypts data using AES. + /// + [Block("Decrypts data with AES", name = "AES Decrypt")] + public static byte[] AESDecrypt(BotData data, byte[] cipherText, byte[] key, byte[] iv, + CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) + { + data.Logger.LogHeader(); + + var plainText = RuriLib.Functions.Crypto.Crypto.AESDecrypt(cipherText, key, iv, mode, padding, keySize); + data.Logger.Log($"Decrypted: {HexConverter.ToHexString(plainText)}", LogColors.YellowGreen); + return plainText; + } - [Block("Decrypts data with AES to string", name = "AES Decrypt String")] - public static string AESDecryptString(BotData data, byte[] cipherText, byte[] key, byte[] iv, - CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) - { - var plainText = RuriLib.Functions.Crypto.Crypto.AESDecryptString(cipherText, key, iv, mode, padding, keySize); - data.Logger.LogHeader(); - data.Logger.Log($"Decrypted: {plainText}", LogColors.YellowGreen); - return plainText; - } + /// + /// Decrypts data with AES to string. + /// + [Block("Decrypts data with AES to string", name = "AES Decrypt String")] + public static string AESDecryptString(BotData data, byte[] cipherText, byte[] key, byte[] iv, + CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.None, int keySize = 256) + { + data.Logger.LogHeader(); + + var plainText = RuriLib.Functions.Crypto.Crypto.AESDecryptString(cipherText, key, iv, mode, padding, keySize); + data.Logger.Log($"Decrypted: {plainText}", LogColors.YellowGreen); + return plainText; + } - [Block("Generates a JSON Web Token using a secret key, payload, optional extra headers and specified algorithm type", - name = "JWT Encode", extraInfo = "The header already contains the selected algorithm and token type (JWT) by default. For JWTs using asymmetric key signatures, the secret must be provided in PEM format.")] - public static string JwtEncode(BotData data, JwtAlgorithmName algorithm, string secret, string extraHeaders = "{}", string payload = "{}") - { - var extraHeadersDictionary = JsonConvert.DeserializeObject>(extraHeaders); - var payloadDictionary = JsonConvert.DeserializeObject>(payload); + /// + /// Generates a JSON Web Token. + /// + [Block("Generates a JSON Web Token using a secret key, payload, optional extra headers and specified algorithm type", + name = "JWT Encode", extraInfo = "The header already contains the selected algorithm and token type (JWT) by default. For JWTs using asymmetric key signatures, the secret must be provided in PEM format.")] + public static string JwtEncode(BotData data, JwtAlgorithmName algorithm, string secret, string extraHeaders = "{}", string payload = "{}") + { + data.Logger.LogHeader(); + + var extraHeadersDictionary = JsonConvert.DeserializeObject>(extraHeaders); + var payloadDictionary = JsonConvert.DeserializeObject>(payload); + + ArgumentNullException.ThrowIfNull(extraHeadersDictionary, nameof(extraHeadersDictionary)); + ArgumentNullException.ThrowIfNull(payloadDictionary, nameof(payloadDictionary)); - string encoded = RuriLib.Functions.Crypto.Crypto.JwtEncode(algorithm, secret, extraHeadersDictionary, payloadDictionary); + var encoded = RuriLib.Functions.Crypto.Crypto.JwtEncode(algorithm, secret, extraHeadersDictionary, payloadDictionary); - data.Logger.LogHeader(); - data.Logger.Log($"Encoded: {encoded}", LogColors.YellowGreen); + data.Logger.Log($"Encoded: {encoded}", LogColors.YellowGreen); - return encoded; - } + return encoded; + } - [Block("Generates a BCrypt hash from an input and a salt", name = "BCrypt Hash", - extraInfo = "If you don't have the salt, use the BCrypt Hash (Gen Salt) block")] - public static string BCryptHash(BotData data, string input, string salt) - { - data.Logger.LogHeader(); + /// + /// Generates a BCrypt hash from an input and a salt. + /// + [Block("Generates a BCrypt hash from an input and a salt", name = "BCrypt Hash", + extraInfo = "If you don't have the salt, use the BCrypt Hash (Gen Salt) block")] + public static string BCryptHash(BotData data, string input, string salt) + { + data.Logger.LogHeader(); - var hashed = RuriLib.Functions.Crypto.Crypto.BCryptWithSalt(input, salt); - data.Logger.Log($"Hashed: {hashed}", LogColors.YellowGreen); + var hashed = RuriLib.Functions.Crypto.Crypto.BCryptWithSalt(input, salt); + data.Logger.Log($"Hashed: {hashed}", LogColors.YellowGreen); - return hashed; - } + return hashed; + } - [Block("Generates a BCrypt hash from an input by generating a salt", name = "BCrypt Hash (Gen Salt)", - extraInfo = "bcryptjs uses salt revision 2X by default currently")] - public static string BCryptHashGenSalt(BotData data, string input, int rounds = 10, SaltRevision saltRevision = SaltRevision.Revision2X) - { - data.Logger.LogHeader(); + /// + /// Generates a BCrypt hash from an input by generating a salt. + /// + [Block("Generates a BCrypt hash from an input by generating a salt", name = "BCrypt Hash (Gen Salt)", + extraInfo = "bcryptjs uses salt revision 2X by default currently")] + public static string BCryptHashGenSalt(BotData data, string input, int rounds = 10, SaltRevision saltRevision = SaltRevision.Revision2X) + { + data.Logger.LogHeader(); - var hashed = RuriLib.Functions.Crypto.Crypto.BCryptGenSalt(input, rounds, saltRevision); - data.Logger.Log($"Hashed: {hashed}", LogColors.YellowGreen); + var hashed = RuriLib.Functions.Crypto.Crypto.BCryptGenSalt(input, rounds, saltRevision); + data.Logger.Log($"Hashed: {hashed}", LogColors.YellowGreen); - return hashed; - } + return hashed; + } - [Block("Verifies that a BCrypt hash is valid", name = "BCrypt Verify")] - public static bool BCryptVerify(BotData data, string input, string hash) - { - data.Logger.LogHeader(); + /// + /// Verifies that a BCrypt hash is valid. + /// + [Block("Verifies that a BCrypt hash is valid", name = "BCrypt Verify")] + public static bool BCryptVerify(BotData data, string input, string hash) + { + data.Logger.LogHeader(); - var isValid = RuriLib.Functions.Crypto.Crypto.BCryptVerify(input, hash); - data.Logger.Log($"BCrypt hash verification result: {isValid}", LogColors.YellowGreen); + var isValid = RuriLib.Functions.Crypto.Crypto.BCryptVerify(input, hash); + data.Logger.Log($"BCrypt hash verification result: {isValid}", LogColors.YellowGreen); - return isValid; - } + return isValid; + } - [Block("Generates an AWS4 Signature from a key, date, region and service", name = "AWS4 Signature", - extraInfo = "It returns a byte array and it expects the date to be in the following format: YYYYMMDD")] - public static byte[] AWS4Signature(BotData data, string key, string date, string region, string service) - { - var signature = RuriLib.Functions.Crypto.Crypto.AWS4Encrypt(key, date, region, service); - - data.Logger.LogHeader(); - data.Logger.Log($"Hashed: {HexConverter.ToHexString(signature)}", LogColors.YellowGreen); + /// + /// Generates an AWS4 Signature from a key, date, region and service. + /// + [Block("Generates an AWS4 Signature from a key, date, region and service", name = "AWS4 Signature", + extraInfo = "It returns a byte array and it expects the date to be in the following format: YYYYMMDD")] + public static byte[] AWS4Signature(BotData data, string key, string date, string region, string service) + { + data.Logger.LogHeader(); + + var signature = RuriLib.Functions.Crypto.Crypto.AWS4Encrypt(key, date, region, service); + data.Logger.Log($"Hashed: {HexConverter.ToHexString(signature)}", LogColors.YellowGreen); - return signature; - } + return signature; } } diff --git a/RuriLib/Blocks/Functions/DictionaryFunctions/Methods.cs b/RuriLib/Blocks/Functions/DictionaryFunctions/Methods.cs index a2146a947..214daa114 100644 --- a/RuriLib/Blocks/Functions/DictionaryFunctions/Methods.cs +++ b/RuriLib/Blocks/Functions/DictionaryFunctions/Methods.cs @@ -4,37 +4,50 @@ using System.Collections.Generic; using System.Linq; -namespace RuriLib.Blocks.Functions.Dictionary +// ReSharper disable once CheckNamespace +namespace RuriLib.Blocks.Functions.Dictionary; + +/// +/// Blocks for working with dictionaries. +/// +[BlockCategory("Dictionary Functions", "Blocks for working with dictionaries", "#9acd32")] +public static class Methods { - [BlockCategory("Dictionary Functions", "Blocks for working with dictionaries", "#9acd32")] - public static class Methods + /// + /// Adds an item to the dictionary. + /// + [Block("Adds an item to the dictionary")] + public static void AddKeyValuePair(BotData data, [Variable] Dictionary dictionary, string key, string value) { - [Block("Adds an item to the dictionary")] - public static void AddKeyValuePair(BotData data, [Variable] Dictionary dictionary, string key, string value) - { - dictionary.Add(key, value); - data.Logger.LogHeader(); - data.Logger.Log($"Added ({key}, {value})", LogColors.YellowGreen); - } + data.Logger.LogHeader(); + + dictionary.Add(key, value); + data.Logger.Log($"Added ({key}, {value})", LogColors.YellowGreen); + } - [Block("Removes an item with a given key from the dictionary")] - public static void RemoveByKey(BotData data, [Variable] Dictionary dictionary, string key) - { - data.Logger.LogHeader(); + /// + /// Gets the value of a key in the dictionary. + /// + [Block("Removes an item with a given key from the dictionary")] + public static void RemoveByKey(BotData data, [Variable] Dictionary dictionary, string key) + { + data.Logger.LogHeader(); - if (dictionary.Remove(key)) - data.Logger.Log($"Removed the item with key {key}", LogColors.YellowGreen); - else - data.Logger.Log($"Could not find an item with key {key}", LogColors.YellowGreen); - } + var removed = dictionary.Remove(key); + data.Logger.Log(removed ? $"Removed the item with key {key}" : $"Could not find an item with key {key}", + LogColors.YellowGreen); + } - [Block("Gets a dictionary key by value (old )")] - public static string GetKey(BotData data, [Variable] Dictionary dictionary, string value) - { - var key = dictionary.FirstOrDefault(kvp => kvp.Value == value).Key; - data.Logger.LogHeader(); - data.Logger.Log($"Got key: {key}", LogColors.YellowGreen); - return key; - } + /// + /// Gets the value of a key in the dictionary. + /// + [Block("Gets a dictionary key by value (old )")] + public static string GetKey(BotData data, [Variable] Dictionary dictionary, string value) + { + data.Logger.LogHeader(); + + var key = dictionary.FirstOrDefault(kvp => kvp.Value == value).Key; + data.Logger.Log($"Got key: {key}", LogColors.YellowGreen); + return key; } } diff --git a/RuriLib/Blocks/Functions/FloatFunctions/Methods.cs b/RuriLib/Blocks/Functions/FloatFunctions/Methods.cs index 5a5085eef..7af454284 100644 --- a/RuriLib/Blocks/Functions/FloatFunctions/Methods.cs +++ b/RuriLib/Blocks/Functions/FloatFunctions/Methods.cs @@ -5,81 +5,116 @@ using System; using System.Data; -namespace RuriLib.Blocks.Functions.Float +// ReSharper disable once CheckNamespace +namespace RuriLib.Blocks.Functions.Float; + +/// +/// Blocks for working with floating point numbers. +/// +[BlockCategory("Float Functions", "Blocks for working with floating point numbers", "#9acd32")] +public static class Methods { - [BlockCategory("Float Functions", "Blocks for working with floating point numbers", "#9acd32")] - public static class Methods + /// + /// Rounds the value up to the nearest integer. + /// + [Block("Rounds the value up to the nearest integer")] + public static int Ceil(BotData data, [Variable] float input) { - [Block("Rounds the value up to the nearest integer")] - public static int Ceil(BotData data, [Variable] float input) - { - var rounded = Convert.ToInt32(Math.Ceiling(input)); - data.Logger.LogHeader(); - data.Logger.Log($"Rounded {input.AsString()} to {rounded}", LogColors.YellowGreen); - return rounded; - } + data.Logger.LogHeader(); + + var rounded = Convert.ToInt32(Math.Ceiling(input)); + data.Logger.Log($"Rounded {input.AsString()} to {rounded}", LogColors.YellowGreen); + return rounded; + } - [Block("Rounds the value down to the nearest integer")] - public static int Floor(BotData data, [Variable] float input) - { - var rounded = Convert.ToInt32(Math.Floor(input)); - data.Logger.LogHeader(); - data.Logger.Log($"Rounded {input.AsString()} to {rounded}", LogColors.YellowGreen); - return rounded; - } + /// + /// Rounds the value down to the nearest integer. + /// + [Block("Rounds the value down to the nearest integer")] + public static int Floor(BotData data, [Variable] float input) + { + data.Logger.LogHeader(); + + var rounded = Convert.ToInt32(Math.Floor(input)); + data.Logger.Log($"Rounded {input.AsString()} to {rounded}", LogColors.YellowGreen); + return rounded; + } - [Block("Rounds the value to the nearest integer")] - public static int RoundToInteger(BotData data, [Variable] float input) - { - var rounded = Convert.ToInt32(Round(data, input, 0)); - data.Logger.LogHeader(); - data.Logger.Log($"Rounded {input.AsString()} to {rounded}", LogColors.YellowGreen); - return rounded; - } + /// + /// Rounds the value to the nearest integer. + /// + [Block("Rounds the value to the nearest integer")] + public static int RoundToInteger(BotData data, [Variable] float input) + { + data.Logger.LogHeader(); + + var rounded = Convert.ToInt32(Round(data, input, 0)); + data.Logger.Log($"Rounded {input.AsString()} to {rounded}", LogColors.YellowGreen); + return rounded; + } - [Block("Rounds the value to the given decimal places")] - public static float Round(BotData data, [Variable] float input, int decimalPlaces = 2) - { - var rounded = Convert.ToSingle(Math.Round(input, decimalPlaces, MidpointRounding.AwayFromZero)); - data.Logger.LogHeader(); - data.Logger.Log($"Rounded {input.AsString()} to {rounded.AsString()}", LogColors.YellowGreen); - return rounded; - } + /// + /// Rounds the value to the given decimal places. + /// + [Block("Rounds the value to the given decimal places")] + public static float Round(BotData data, [Variable] float input, int decimalPlaces = 2) + { + data.Logger.LogHeader(); + + var rounded = Convert.ToSingle(Math.Round(input, decimalPlaces, MidpointRounding.AwayFromZero)); + data.Logger.Log($"Rounded {input.AsString()} to {rounded.AsString()}", LogColors.YellowGreen); + return rounded; + } - [Block("Computes the value of a given mathematical expression")] - public static float Compute(BotData data, [Variable] string input) - { - var result = Convert.ToSingle(new DataTable().Compute(input.Replace(',', '.'), null)); - data.Logger.LogHeader(); - data.Logger.Log($"Computed {input} with result {result.AsString()}", LogColors.YellowGreen); - return result; - } + /// + /// Computes the value of a given mathematical expression. + /// + [Block("Computes the value of a given mathematical expression")] + public static float Compute(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var result = Convert.ToSingle(new DataTable().Compute(input.Replace(',', '.'), null)); + data.Logger.Log($"Computed {input} with result {result.AsString()}", LogColors.YellowGreen); + return result; + } - [Block("Generates a random float between two values (inclusive)")] - public static float RandomFloat(BotData data, float minimum = 0, float maximum = 1) - { - var random = Convert.ToSingle(data.Random.NextDouble()) * (maximum - minimum) + minimum; - data.Logger.LogHeader(); - data.Logger.Log($"Generated random value {random.AsString()} in the interval ({minimum.AsString()},{maximum.AsString()})", LogColors.YellowGreen); - return random; - } + /// + /// Generates a random float between two values (inclusive). + /// + [Block("Generates a random float between two values (inclusive)")] + public static float RandomFloat(BotData data, float minimum = 0, float maximum = 1) + { + data.Logger.LogHeader(); + + var random = Convert.ToSingle(data.Random.NextDouble()) * (maximum - minimum) + minimum; + data.Logger.Log($"Generated random value {random.AsString()} in the interval ({minimum.AsString()},{maximum.AsString()})", LogColors.YellowGreen); + return random; + } - [Block("Takes the maximum between two floats", name = "Maximum Float")] - public static float TakeMaxFloat(BotData data, float first, float second) - { - var max = Math.Max(first, second); - data.Logger.LogHeader(); - data.Logger.Log($"The maximum between {first} and {second} is {max}", LogColors.YellowGreen); - return max; - } + /// + /// Takes the maximum between two floats. + /// + [Block("Takes the maximum between two floats", name = "Maximum Float")] + public static float TakeMaxFloat(BotData data, float first, float second) + { + data.Logger.LogHeader(); + + var max = Math.Max(first, second); + data.Logger.Log($"The maximum between {first} and {second} is {max}", LogColors.YellowGreen); + return max; + } - [Block("Takes the minimum between two floats", name = "Minimum Float")] - public static float TakeMinFloat(BotData data, float first, float second) - { - var min = Math.Min(first, second); - data.Logger.LogHeader(); - data.Logger.Log($"The minimum between {first} and {second} is {min}", LogColors.YellowGreen); - return min; - } + /// + /// Takes the minimum between two floats. + /// + [Block("Takes the minimum between two floats", name = "Minimum Float")] + public static float TakeMinFloat(BotData data, float first, float second) + { + data.Logger.LogHeader(); + + var min = Math.Min(first, second); + data.Logger.Log($"The minimum between {first} and {second} is {min}", LogColors.YellowGreen); + return min; } } diff --git a/RuriLib/Blocks/Functions/IntegerFunctions/Methods.cs b/RuriLib/Blocks/Functions/IntegerFunctions/Methods.cs index ba2c85b02..bcd95623b 100644 --- a/RuriLib/Blocks/Functions/IntegerFunctions/Methods.cs +++ b/RuriLib/Blocks/Functions/IntegerFunctions/Methods.cs @@ -1,41 +1,53 @@ using RuriLib.Attributes; -using RuriLib.Functions.Parsing; using RuriLib.Logging; using RuriLib.Models.Bots; using System; -using System.Collections.Generic; -using System.Linq; -namespace RuriLib.Blocks.Functions.Integer +// ReSharper disable once CheckNamespace +namespace RuriLib.Blocks.Functions.Integer; + +/// +/// Blocks for working with integer numbers. +/// +[BlockCategory("Integer Functions", "Blocks for working with integer numbers", "#9acd32")] +public static class Methods { - [BlockCategory("Integer Functions", "Blocks for working with integer numbers", "#9acd32")] - public static class Methods + /// + /// Generates a random integer between two values (inclusive). + /// + [Block("Generates a random integer between two values (inclusive)")] + public static int RandomInteger(BotData data, int minimum = 0, int maximum = 10) { - [Block("Generates a random integer between two values (inclusive)")] - public static int RandomInteger(BotData data, int minimum = 0, int maximum = 10) - { - var random = data.Random.Next(minimum, maximum + 1); - data.Logger.LogHeader(); - data.Logger.Log($"Generated random value {random} in the interval ({minimum},{maximum})", LogColors.YellowGreen); - return random; - } + data.Logger.LogHeader(); + + var random = data.Random.Next(minimum, maximum + 1); + data.Logger.Log($"Generated random value {random} in the interval ({minimum},{maximum})", LogColors.YellowGreen); + return random; + } - [Block("Takes the maximum between two integers", name = "Maximum Int")] - public static int TakeMaxInt(BotData data, int first, int second) - { - var max = Math.Max(first, second); - data.Logger.LogHeader(); - data.Logger.Log($"The maximum between {first} and {second} is {max}", LogColors.YellowGreen); - return max; - } + /// + /// Takes the maximum between two integers. + /// + [Block("Takes the maximum between two integers", name = "Maximum Int")] + public static int TakeMaxInt(BotData data, int first, int second) + { + data.Logger.LogHeader(); + + var max = Math.Max(first, second); + data.Logger.Log($"The maximum between {first} and {second} is {max}", LogColors.YellowGreen); + return max; + } - [Block("Takes the minimum between two integers", name = "Minimum int")] - public static int TakeMinInt(BotData data, int first, int second) - { - var min = Math.Min(first, second); - data.Logger.LogHeader(); - data.Logger.Log($"The minimum between {first} and {second} is {min}", LogColors.YellowGreen); - return min; - } + /// + /// Takes the minimum between two integers. + /// + [Block("Takes the minimum between two integers", name = "Minimum int")] + public static int TakeMinInt(BotData data, int first, int second) + { + data.Logger.LogHeader(); + + var min = Math.Min(first, second); + data.Logger.Log($"The minimum between {first} and {second} is {min}", LogColors.YellowGreen); + return min; } } diff --git a/RuriLib/Blocks/Functions/ListFunctions/Methods.cs b/RuriLib/Blocks/Functions/ListFunctions/Methods.cs index 2b3102284..0974815c7 100644 --- a/RuriLib/Blocks/Functions/ListFunctions/Methods.cs +++ b/RuriLib/Blocks/Functions/ListFunctions/Methods.cs @@ -8,210 +8,261 @@ using System.Globalization; using System.Linq; -namespace RuriLib.Blocks.Functions.List +// ReSharper disable once CheckNamespace +namespace RuriLib.Blocks.Functions.List; + +/// +/// Blocks for working with lists of strings. +/// +[BlockCategory("List Functions", "Blocks for working with lists of strings", "#9acd32")] +public static class Methods { - [BlockCategory("List Functions", "Blocks for working with lists of strings", "#9acd32")] - public static class Methods + /// + /// Counts the number of elements in the list. + /// + [Block("Counts the number of elements in the list")] + public static int GetListLength(BotData data, [Variable] List list) { - [Block("Counts the number of elements in the list")] - public static int GetListLength(BotData data, [Variable] List list) - { - var count = list.Count; - data.Logger.LogHeader(); - data.Logger.Log($"The list has {count} elements", LogColors.YellowGreen); - return count; - } + data.Logger.LogHeader(); + + var count = list.Count; + data.Logger.Log($"The list has {count} elements", LogColors.YellowGreen); + return count; + } - [Block("Joins all the strings in the list to create a single string with the given separator")] - public static string JoinList(BotData data, [Variable] List list, string separator = ",") + /// + /// Joins all the strings in the list to create a single string with the given separator. + /// + [Block("Joins all the strings in the list to create a single string with the given separator")] + public static string JoinList(BotData data, [Variable] List list, string separator = ",") + { + data.Logger.LogHeader(); + + var joined = string.Join(separator, list); + data.Logger.Log($"Joined string: {joined}", LogColors.YellowGreen); + return joined; + } + + /// + /// Sorts a list alphabetically. + /// + [Block("Sorts a list alphabetically", extraInfo = "If the elements of the list are numeric values, set numeric to true")] + public static void SortList(BotData data, [Variable] List list, bool ascending = true, bool numeric = false) + { + data.Logger.LogHeader(); + + if (numeric) { - var joined = string.Join(separator, list); - data.Logger.LogHeader(); - data.Logger.Log($"Joined string: {joined}", LogColors.YellowGreen); - return joined; + var nums = list.Select(e => double.Parse(e, CultureInfo.InvariantCulture)).ToList(); + nums.Sort(); + list.Clear(); + list.AddRange(nums.Select(e => e.ToString(CultureInfo.InvariantCulture))); } - - [Block("Sorts a list alphabetically", extraInfo = "If the elements of the list are numeric values, set numeric to true")] - public static void SortList(BotData data, [Variable] List list, bool ascending = true, bool numeric = false) + else { - if (numeric) - { - var nums = list.Select(e => double.Parse(e, CultureInfo.InvariantCulture)).ToList(); - nums.Sort(); - list.Clear(); - list.AddRange(nums.Select(e => e.ToString())); - } - else - { - list.Sort(); - } - - if (!ascending) - list.Reverse(); - - data.Logger.LogHeader(); - data.Logger.Log("Sorted list in " + (ascending ? "ascending" : "descending") + " order", LogColors.YellowGreen); + list.Sort(); } - [Block("Concatenates two lists into a single one")] - public static List ConcatLists(BotData data, [Variable] List list1, [Variable] List list2) + if (!ascending) { - var concat = list1.Concat(list2).ToList(); - data.Logger.LogHeader(); - data.Logger.Log("Concatenated the lists", LogColors.YellowGreen); - return concat; + list.Reverse(); } - [Block("Zips two lists into a single one", - extraInfo = "You can specify the format for joined elements. " + - "[0] is replaced with an element from the first list, and [1] with an element from the second list. " + - "For example if the format is [0][1], the lists [1,2] and [a,b] will be zipped into [1a,2b]")] - public static List ZipLists(BotData data, [Variable] List list1, [Variable] List list2, - bool fill = false, string fillString = "NULL", string format = "[0][1]") - { - List zipped; - Func zipFunc = (a, b) => format.Replace("[0]", a).Replace("[1]", b); + data.Logger.Log("Sorted list in " + (ascending ? "ascending" : "descending") + " order", LogColors.YellowGreen); + } - if (fill) - { - zipped = list1.Count < list2.Count - ? list1.Concat(Enumerable.Repeat(fillString, list2.Count - list2.Count)).Zip(list2, zipFunc).ToList() - : list1.Zip(list2.Concat(Enumerable.Repeat(fillString, list1.Count - list2.Count)), zipFunc).ToList(); - } - else - { - zipped = list1.Zip(list2, zipFunc).ToList(); - } - - data.Logger.LogHeader(); - data.Logger.Log("Zipped the lists", LogColors.YellowGreen); - return zipped; - } + /// + /// Concatenates two lists into a single one. + /// + [Block("Concatenates two lists into a single one")] + public static List ConcatLists(BotData data, [Variable] List list1, [Variable] List list2) + { + data.Logger.LogHeader(); + + var concat = list1.Concat(list2).ToList(); + data.Logger.Log("Concatenated the lists", LogColors.YellowGreen); + return concat; + } + + /// + /// Zips two lists into a single one. + /// + [Block("Zips two lists into a single one", + extraInfo = "You can specify the format for joined elements. " + + "[0] is replaced with an element from the first list, and [1] with an element from the second list. " + + "For example if the format is [0][1], the lists [1,2] and [a,b] will be zipped into [1a,2b]")] + public static List ZipLists(BotData data, [Variable] List list1, [Variable] List list2, + bool fill = false, string fillString = "NULL", string format = "[0][1]") + { + data.Logger.LogHeader(); + + List zipped; + Func zipFunc = (a, b) => format.Replace("[0]", a).Replace("[1]", b); - [Block("Maps two lists into a dictionary", extraInfo = "For example [1,2] and [a,b] will be mapped into {(1,a), (2,b)}")] - public static Dictionary MapLists(BotData data, [Variable] List list1, [Variable] List list2) + if (fill) { - var mapped = list1.Zip(list2, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v); - data.Logger.LogHeader(); - data.Logger.Log("Mapped the lists", LogColors.YellowGreen); - return mapped; + zipped = list1.Count < list2.Count + ? list1.Concat(Enumerable.Repeat(fillString, list2.Count - list2.Count)).Zip(list2, zipFunc).ToList() + : list1.Zip(list2.Concat(Enumerable.Repeat(fillString, list1.Count - list2.Count)), zipFunc).ToList(); } - - [Block("Adds an item to a list", - extraInfo = "If the index is negative, it will start from the end of the list. For example an index of -1 will add the item at the end of the list")] - public static void AddToList(BotData data, [Variable] List list, string item, int index = -1) + else { - if (list.Count == 0) index = 0; - else if (index < 0) index += list.Count + 1; - list.Insert(index, item); - - data.Logger.LogHeader(); - data.Logger.Log($"Added {item} at index {index}", LogColors.YellowGreen); + zipped = list1.Zip(list2, zipFunc).ToList(); } + + data.Logger.Log("Zipped the lists", LogColors.YellowGreen); + return zipped; + } - [Block("Removes an item from a list", - extraInfo = "If the index is negative, it will start from the end of the list. For example an index of -1 will remove the item at the end of the list")] - public static void RemoveFromList(BotData data, [Variable] List list, int index = 0) - { - if (list.Count == 0) index = 0; - else if (index < 0) index += list.Count +1 ; - var removedItem = list[index]; - list.RemoveAt(index); + [Block("Maps two lists into a dictionary", extraInfo = "For example [1,2] and [a,b] will be mapped into {(1,a), (2,b)}")] + public static Dictionary MapLists(BotData data, [Variable] List list1, [Variable] List list2) + { + data.Logger.LogHeader(); + + var mapped = list1.Zip(list2, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v); + data.Logger.Log("Mapped the lists", LogColors.YellowGreen); + return mapped; + } - data.Logger.LogHeader(); - data.Logger.Log($"Removed item {removedItem} at index {index}", LogColors.YellowGreen); - } + /// + /// Adds an item to a list. + /// + [Block("Adds an item to a list", + extraInfo = "If the index is negative, it will start from the end of the list. For example an index of -1 will add the item at the end of the list")] + public static void AddToList(BotData data, [Variable] List list, string item, int index = -1) + { + data.Logger.LogHeader(); - [Block("Removes all items that match a given condition from a list")] - public static void RemoveAllFromList(BotData data, [Variable] List list, StrComparison comparison, string term) + if (list.Count == 0) { - var count = list.RemoveAll(i => RuriLib.Functions.Conditions.Conditions.Check(i, comparison, term)); - - data.Logger.LogHeader(); - data.Logger.Log($"Removed {count} items", LogColors.YellowGreen); + index = 0; } - - [Block("Removes duplicate items from a list")] - public static List RemoveDuplicates(BotData data, [Variable] List list) + else if (index < 0) { - var unique = list.Distinct().ToList(); - data.Logger.LogHeader(); - data.Logger.Log($"Removed {list.Count - unique.Count} duplicates", LogColors.YellowGreen); - return unique; + index += list.Count + 1; } + + list.Insert(index, item); - [Block("Gets a random item from a list")] - public static string GetRandomItem(BotData data, [Variable] List list) + data.Logger.Log($"Added {item} at index {index}", LogColors.YellowGreen); + } + + /// + /// Removes an item from a list. + /// + [Block("Removes an item from a list", + extraInfo = "If the index is negative, it will start from the end of the list. For example an index of -1 will remove the item at the end of the list")] + public static void RemoveFromList(BotData data, [Variable] List list, int index = 0) + { + data.Logger.LogHeader(); + + if (list.Count == 0) { - var item = list[data.Random.Next(list.Count)]; - data.Logger.LogHeader(); - data.Logger.Log($"Got random item: {item}", LogColors.YellowGreen); - return item; + index = 0; } - - [Block("Shuffles the items of a list")] - public static void Shuffle(BotData data, [Variable] List list) + else if (index < 0) { - list.Shuffle(data.Random); - data.Logger.LogHeader(); - data.Logger.Log("Shuffled the list", LogColors.YellowGreen); + index += list.Count +1 ; } + + var removedItem = list[index]; + list.RemoveAt(index); - [Block("Splits the items of a list to create a dictionary", name = "To Dictionary")] - public static Dictionary ListToDictionary(BotData data, [Variable] List list, - string separator = ":", bool autoTrim = true) - { - Dictionary dict = new(); + data.Logger.Log($"Removed item {removedItem} at index {index}", LogColors.YellowGreen); + } - foreach (var item in list) - { - var split = item.Split(separator, 2, autoTrim ? StringSplitOptions.TrimEntries : StringSplitOptions.None); + [Block("Removes all items that match a given condition from a list")] + public static void RemoveAllFromList(BotData data, [Variable] List list, StrComparison comparison, string term) + { + data.Logger.LogHeader(); + + var count = list.RemoveAll(i => RuriLib.Functions.Conditions.Conditions.Check(i, comparison, term)); - if (!string.IsNullOrEmpty(split[0])) - { - dict[split[0]] = split[1]; - } - } + data.Logger.Log($"Removed {count} items", LogColors.YellowGreen); + } - data.Logger.LogHeader(); - data.Logger.Log("Split the list into a dictionary", LogColors.YellowGreen); - return dict; - } + [Block("Removes duplicate items from a list")] + public static List RemoveDuplicates(BotData data, [Variable] List list) + { + data.Logger.LogHeader(); + + var unique = list.Distinct().ToList(); + data.Logger.Log($"Removed {list.Count - unique.Count} duplicates", LogColors.YellowGreen); + return unique; + } - [Block("Gets the index of an element of a list", name = "Index Of")] - public static int ListIndexOf(BotData data, [Variable] List list, string item, bool exactMatch = false, - bool caseSensitive = false) - { - var comparer = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; - var elem = list.FirstOrDefault(i => exactMatch - ? i.Equals(item, comparer) - : i.Contains(item, comparer)); + [Block("Gets a random item from a list")] + public static string GetRandomItem(BotData data, [Variable] List list) + { + data.Logger.LogHeader(); + + var item = list[data.Random.Next(list.Count)]; + data.Logger.Log($"Got random item: {item}", LogColors.YellowGreen); + return item; + } - data.Logger.LogHeader(); + [Block("Shuffles the items of a list")] + public static void Shuffle(BotData data, [Variable] List list) + { + data.Logger.LogHeader(); + + list.Shuffle(data.Random); + data.Logger.Log("Shuffled the list", LogColors.YellowGreen); + } - if (elem is null) - { - data.Logger.Log("Item not found in the list, returning -1", LogColors.YellowGreen); - return -1; - } - else + [Block("Splits the items of a list to create a dictionary", name = "To Dictionary")] + public static Dictionary ListToDictionary(BotData data, [Variable] List list, + string separator = ":", bool autoTrim = true) + { + data.Logger.LogHeader(); + + Dictionary dict = new(); + + foreach (var item in list) + { + var split = item.Split(separator, 2, autoTrim ? StringSplitOptions.TrimEntries : StringSplitOptions.None); + + if (!string.IsNullOrEmpty(split[0])) { - var index = list.IndexOf(elem); - data.Logger.Log($"Item found at index {index}", LogColors.YellowGreen); - return index; + dict[split[0]] = split[1]; } } - [Block("Creates a list of N numbers starting from the specified number", name = "Create List of Numbers")] - public static List CreateListOfNumbers(BotData data, int start, int count) + data.Logger.Log("Split the list into a dictionary", LogColors.YellowGreen); + return dict; + } + + [Block("Gets the index of an element of a list", name = "Index Of")] + public static int ListIndexOf(BotData data, [Variable] List list, string item, bool exactMatch = false, + bool caseSensitive = false) + { + data.Logger.LogHeader(); + + var comparer = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + var elem = list.FirstOrDefault(i => exactMatch + ? i.Equals(item, comparer) + : i.Contains(item, comparer)); + + if (elem is null) { - data.Logger.LogHeader(); + data.Logger.Log("Item not found in the list, returning -1", LogColors.YellowGreen); + return -1; + } - var list = Enumerable.Range(start, count).Select(i => i.ToString()).ToList(); + var index = list.IndexOf(elem); + data.Logger.Log($"Item found at index {index}", LogColors.YellowGreen); + return index; + } - data.Logger.Log($"Created a list of {count} numbers starting from {start}", LogColors.YellowGreen); + [Block("Creates a list of N numbers starting from the specified number", name = "Create List of Numbers")] + public static List CreateListOfNumbers(BotData data, int start, int count) + { + data.Logger.LogHeader(); - return list; - } + var list = Enumerable.Range(start, count).Select(i => i.ToString()).ToList(); + + data.Logger.Log($"Created a list of {count} numbers starting from {start}", LogColors.YellowGreen); + + return list; } } diff --git a/RuriLib/Blocks/Functions/Methods.cs b/RuriLib/Blocks/Functions/Methods.cs index d36a0ccf4..b172f3f50 100644 --- a/RuriLib/Blocks/Functions/Methods.cs +++ b/RuriLib/Blocks/Functions/Methods.cs @@ -2,29 +2,32 @@ using RuriLib.Models.Bots; using RuriLib.Providers.UserAgents; -namespace RuriLib.Blocks.Functions +namespace RuriLib.Blocks.Functions; + +[BlockCategory("Functions", "General purpose functions", "#9acd32")] +public static class Methods { - [BlockCategory("Functions", "General purpose functions", "#9acd32")] - public static class Methods + /// + /// Generates a random User Agent using the builtin provider or a custom list. + /// + [Block("Generates a random User Agent using the builtin provider or a custom list")] + public static string RandomUserAgent(BotData data, UAPlatform platform = UAPlatform.All) { - [Block("Generates a random User Agent using the builtin provider or a custom list")] - public static string RandomUserAgent(BotData data, UAPlatform platform = UAPlatform.All) - { - data.Logger.LogHeader(); - data.Logger.Log("Getting random UA from the builtin provider"); - string ua; + data.Logger.LogHeader(); + + data.Logger.Log("Getting random UA from the builtin provider"); + string ua; - try - { - ua = data.Providers.RandomUA.Generate(platform); - } - catch - { - ua = "NO_RANDOM_UA_FOUND"; - } - - data.Logger.Log(ua); - return ua; + try + { + ua = data.Providers.RandomUA.Generate(platform); } + catch + { + ua = "NO_RANDOM_UA_FOUND"; + } + + data.Logger.Log(ua); + return ua; } } diff --git a/RuriLib/Blocks/Functions/StringFunctions/Methods.cs b/RuriLib/Blocks/Functions/StringFunctions/Methods.cs index fcd391681..bd0b58745 100644 --- a/RuriLib/Blocks/Functions/StringFunctions/Methods.cs +++ b/RuriLib/Blocks/Functions/StringFunctions/Methods.cs @@ -9,215 +9,245 @@ using System.Text; using System.Text.RegularExpressions; -namespace RuriLib.Blocks.Functions.String +// ReSharper disable once CheckNamespace +namespace RuriLib.Blocks.Functions.String; + +[BlockCategory("String Functions", "Blocks for working with strings", "#9acd32")] +public static class Methods { - [BlockCategory("String Functions", "Blocks for working with strings", "#9acd32")] - public static class Methods - { - #region RandomString fields - private static readonly string _lowercase = "abcdefghijklmnopqrstuvwxyz"; - private static readonly string _uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static readonly string _digits = "0123456789"; - private static readonly string _symbols = "\\!\"£$%&/()=?^'{}[]@#,;.:-_*+"; - private static readonly string _hex = _digits + "abcdef"; - private static readonly string _udChars = _uppercase + _digits; - private static readonly string _ldChars = _lowercase + _digits; - private static readonly string _upperlwr = _lowercase + _uppercase; - private static readonly string _ludChars = _lowercase + _uppercase + _digits; - private static readonly string _allChars = _lowercase + _uppercase + _digits + _symbols; - #endregion - - [Block("Rounds the value down to the nearest integer")] - public static int CountOccurrences(BotData data, [Variable] string input, string word) - { - var occurrences = input.CountOccurrences(word); - data.Logger.LogHeader(); - data.Logger.Log($"Found {occurrences} occurrences of {word}", LogColors.YellowGreen); - return occurrences; - } + #region RandomString fields + + private const string _lowercase = "abcdefghijklmnopqrstuvwxyz"; + private const string _uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private const string _digits = "0123456789"; + private const string _symbols = "\\!\"£$%&/()=?^'{}[]@#,;.:-_*+"; + private const string _hex = _digits + "abcdef"; + private const string _udChars = _uppercase + _digits; + private const string _ldChars = _lowercase + _digits; + private const string _upperLower = _lowercase + _uppercase; + private const string _upperLowerDigits = _lowercase + _uppercase + _digits; + private const string _allChars = _lowercase + _uppercase + _digits + _symbols; + + #endregion + + /// + /// Rounds the value down to the nearest integer. + /// + [Block("Rounds the value down to the nearest integer")] + public static int CountOccurrences(BotData data, [Variable] string input, string word) + { + data.Logger.LogHeader(); + + var occurrences = input.CountOccurrences(word); + data.Logger.Log($"Found {occurrences} occurrences of {word}", LogColors.YellowGreen); + return occurrences; + } - [Block("Retrieves a piece of an input string")] - public static string Substring(BotData data, [Variable] string input, int index, int length) - { - var substring = input.Substring(index, length); - data.Logger.LogHeader(); - data.Logger.Log($"Retrieved substring: {substring}", LogColors.YellowGreen); - return substring; - } + /// + /// Retrieves a piece of an input string. + /// + [Block("Retrieves a piece of an input string")] + public static string Substring(BotData data, [Variable] string input, int index, int length) + { + data.Logger.LogHeader(); + + var substring = input.Substring(index, length); + data.Logger.Log($"Retrieved substring: {substring}", LogColors.YellowGreen); + return substring; + } - [Block("Reverses the characters in the input string")] - public static string Reverse(BotData data, [Variable] string input) - { - char[] charArray = input.ToCharArray(); - Array.Reverse(charArray); - var reversed = new string(charArray); - data.Logger.LogHeader(); - data.Logger.Log($"Reversed {input} with result {reversed}", LogColors.YellowGreen); - return reversed; - } + /// + /// Reverses the characters in the input string. + /// + [Block("Reverses the characters in the input string")] + public static string Reverse(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var charArray = input.ToCharArray(); + Array.Reverse(charArray); + var reversed = new string(charArray); + data.Logger.Log($"Reversed {input} with result {reversed}", LogColors.YellowGreen); + return reversed; + } - [Block("Removes leading or trailing whitespace from the input string")] - public static string Trim(BotData data, [Variable] string input) - { - var trimmed = input.Trim(); - data.Logger.LogHeader(); - data.Logger.Log("Trimmed the input string", LogColors.YellowGreen); - return trimmed; - } + [Block("Removes leading or trailing whitespace from the input string")] + public static string Trim(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var trimmed = input.Trim(); + data.Logger.Log("Trimmed the input string", LogColors.YellowGreen); + return trimmed; + } - [Block("Gets the length of a string")] - public static int Length(BotData data, [Variable] string input) - { - var length = input.Length; - data.Logger.LogHeader(); - data.Logger.Log($"Calculated length: {length}", LogColors.YellowGreen); - return length; - } + [Block("Gets the length of a string")] + public static int Length(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var length = input.Length; + data.Logger.Log($"Calculated length: {length}", LogColors.YellowGreen); + return length; + } - [Block("Changes all letters of a string to uppercase")] - public static string ToUppercase(BotData data, [Variable] string input) - { - var upper = input.ToUpper(); - data.Logger.LogHeader(); - data.Logger.Log($"Converted the input string: {upper}", LogColors.YellowGreen); - return upper; - } + [Block("Changes all letters of a string to uppercase")] + public static string ToUppercase(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var upper = input.ToUpper(); + data.Logger.Log($"Converted the input string: {upper}", LogColors.YellowGreen); + return upper; + } - [Block("Changes all letters of a string to lowercase")] - public static string ToLowercase(BotData data, [Variable] string input) - { - var lower = input.ToLower(); - data.Logger.LogHeader(); - data.Logger.Log($"Converted the input string: {lower}", LogColors.YellowGreen); - return lower; - } + [Block("Changes all letters of a string to lowercase")] + public static string ToLowercase(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var lower = input.ToLower(); + data.Logger.Log($"Converted the input string: {lower}", LogColors.YellowGreen); + return lower; + } - [Block("Replaces all occurrences of some text in a string")] - public static string Replace(BotData data, [Variable] string original, string toReplace, string replacement) - { - var replaced = original.Replace(toReplace, replacement); - data.Logger.LogHeader(); - data.Logger.Log($"Replaced string: {replaced}", LogColors.YellowGreen); - return replaced; - } + [Block("Replaces all occurrences of some text in a string")] + public static string Replace(BotData data, [Variable] string original, string toReplace, string replacement) + { + data.Logger.LogHeader(); + + var replaced = original.Replace(toReplace, replacement); + data.Logger.Log($"Replaced string: {replaced}", LogColors.YellowGreen); + return replaced; + } - [Block("Replaces all regex matches with a given text", - extraInfo = "The replacement can contain regex groups with syntax like $1$2")] - public static string RegexReplace(BotData data, [Variable] string original, string pattern, string replacement) - { - var replaced = Regex.Replace(original, pattern, replacement); - data.Logger.LogHeader(); - data.Logger.Log($"Replaced string: {replaced}", LogColors.YellowGreen); - return replaced; - } + [Block("Replaces all regex matches with a given text", + extraInfo = "The replacement can contain regex groups with syntax like $1$2")] + public static string RegexReplace(BotData data, [Variable] string original, string pattern, string replacement) + { + data.Logger.LogHeader(); + + var replaced = Regex.Replace(original, pattern, replacement); + data.Logger.Log($"Replaced string: {replaced}", LogColors.YellowGreen); + return replaced; + } - [Block("Translates text in a string basing on a dictionary")] - public static string Translate(BotData data, [Variable] string input, Dictionary translations, - bool replaceOne = false) - { - var sb = new StringBuilder(input); - var replacements = 0; + [Block("Translates text in a string basing on a dictionary")] + public static string Translate(BotData data, [Variable] string input, Dictionary translations, + bool replaceOne = false) + { + data.Logger.LogHeader(); + + var sb = new StringBuilder(input); + var replacements = 0; - foreach (var entry in translations.OrderBy(e => e.Key.Length).Reverse()) + foreach (var entry in translations.OrderBy(e => e.Key.Length).Reverse()) + { + if (input.Contains(entry.Key)) { - if (input.Contains(entry.Key)) - { - replacements += input.CountOccurrences(entry.Key); - sb.Replace(entry.Key, entry.Value); - if (replaceOne) break; - } + replacements += input.CountOccurrences(entry.Key); + sb.Replace(entry.Key, entry.Value); + if (replaceOne) break; } + } - var translated = sb.ToString(); - data.Logger.LogHeader(); - data.Logger.Log($"Translated {replacements} occurrence(s). Translated string: {translated}", LogColors.YellowGreen); + var translated = sb.ToString(); + data.Logger.Log($"Translated {replacements} occurrence(s). Translated string: {translated}", LogColors.YellowGreen); - return translated; - } + return translated; + } - [Block("URL encodes a string")] - public static string UrlEncode(BotData data, [Variable] string input) - { - // The maximum allowed Uri size is 2083 characters, we use 2080 as a precaution - var encoded = string.Join("", input.SplitInChunks(2080).Select(Uri.EscapeDataString)); - data.Logger.LogHeader(); - data.Logger.Log($"URL Encoded string: {encoded}", LogColors.YellowGreen); - return encoded; - } + [Block("URL encodes a string")] + public static string UrlEncode(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + // The maximum allowed Uri size is 2083 characters, we use 2080 as a precaution + var encoded = string.Join("", input.SplitInChunks(2080).Select(Uri.EscapeDataString)); + data.Logger.Log($"URL Encoded string: {encoded}", LogColors.YellowGreen); + return encoded; + } - [Block("URL decodes a string")] - public static string UrlDecode(BotData data, [Variable] string input) - { - var decoded = Uri.UnescapeDataString(input); - data.Logger.LogHeader(); - data.Logger.Log($"URL Decoded string: {decoded}", LogColors.YellowGreen); - return decoded; - } + [Block("URL decodes a string")] + public static string UrlDecode(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var decoded = Uri.UnescapeDataString(input); + data.Logger.Log($"URL Decoded string: {decoded}", LogColors.YellowGreen); + return decoded; + } - [Block("Encodes HTML entities in a string")] - public static string EncodeHTMLEntities(BotData data, [Variable] string input) - { - var encoded = WebUtility.HtmlEncode(input); - data.Logger.LogHeader(); - data.Logger.Log($"Encoded string: {encoded}", LogColors.YellowGreen); - return encoded; - } + [Block("Encodes HTML entities in a string")] + public static string EncodeHTMLEntities(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var encoded = WebUtility.HtmlEncode(input); + data.Logger.Log($"Encoded string: {encoded}", LogColors.YellowGreen); + return encoded; + } - [Block("Decodes HTML entities in a string")] - public static string DecodeHTMLEntities(BotData data, [Variable] string input) - { - var decoded = WebUtility.HtmlDecode(input); - data.Logger.LogHeader(); - data.Logger.Log($"Decoded string: {decoded}", LogColors.YellowGreen); - return decoded; - } + [Block("Decodes HTML entities in a string")] + public static string DecodeHTMLEntities(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var decoded = WebUtility.HtmlDecode(input); + data.Logger.Log($"Decoded string: {decoded}", LogColors.YellowGreen); + return decoded; + } - [Block("Generates a random string given a mask", - extraInfo = "?l = Lowercase, ?u = Uppercase, ?d = Digit, ?f = Uppercase + Lowercase, ?s = Symbol, ?h = Hex (Lowercase), ?H = Hex (Uppercase), ?m = Upper + Digits, ?n = Lower + Digits, ?i = Lower + Upper + Digits, ?a = Any, ?c = Custom")] - public static string RandomString(BotData data, string input, string customCharset = "0123456789") - { - input = Regex.Replace(input, @"\?l", m => _lowercase[data.Random.Next(_lowercase.Length)].ToString()); - input = Regex.Replace(input, @"\?u", m => _uppercase[data.Random.Next(_uppercase.Length)].ToString()); - input = Regex.Replace(input, @"\?d", m => _digits[data.Random.Next(_digits.Length)].ToString()); - input = Regex.Replace(input, @"\?s", m => _symbols[data.Random.Next(_symbols.Length)].ToString()); - input = Regex.Replace(input, @"\?h", m => _hex[data.Random.Next(_hex.Length)].ToString()); - input = Regex.Replace(input, @"\?H", m => _hex[data.Random.Next(_hex.Length)].ToString().ToUpper()); - input = Regex.Replace(input, @"\?a", m => _allChars[data.Random.Next(_allChars.Length)].ToString()); - input = Regex.Replace(input, @"\?m", m => _udChars[data.Random.Next(_udChars.Length)].ToString()); - input = Regex.Replace(input, @"\?n", m => _ldChars[data.Random.Next(_ldChars.Length)].ToString()); - input = Regex.Replace(input, @"\?i", m => _ludChars[data.Random.Next(_ludChars.Length)].ToString()); - input = Regex.Replace(input, @"\?f", m => _upperlwr[data.Random.Next(_upperlwr.Length)].ToString()); - input = Regex.Replace(input, @"\?c", m => customCharset[data.Random.Next(customCharset.Length)].ToString()); - data.Logger.LogHeader(); - data.Logger.Log($"Generated string: {input}", LogColors.YellowGreen); - return input; - } + [Block("Generates a random string given a mask", + extraInfo = "?l = Lowercase, ?u = Uppercase, ?d = Digit, ?f = Uppercase + Lowercase, ?s = Symbol, ?h = Hex (Lowercase), ?H = Hex (Uppercase), ?m = Upper + Digits, ?n = Lower + Digits, ?i = Lower + Upper + Digits, ?a = Any, ?c = Custom")] + public static string RandomString(BotData data, string input, string customCharset = "0123456789") + { + data.Logger.LogHeader(); + + // TODO: The performance of this method can be improved by using a StringBuilder + input = Regex.Replace(input, @"\?l", m => _lowercase[data.Random.Next(_lowercase.Length)].ToString()); + input = Regex.Replace(input, @"\?u", m => _uppercase[data.Random.Next(_uppercase.Length)].ToString()); + input = Regex.Replace(input, @"\?d", m => _digits[data.Random.Next(_digits.Length)].ToString()); + input = Regex.Replace(input, @"\?s", m => _symbols[data.Random.Next(_symbols.Length)].ToString()); + input = Regex.Replace(input, @"\?h", m => _hex[data.Random.Next(_hex.Length)].ToString()); + input = Regex.Replace(input, @"\?H", m => _hex[data.Random.Next(_hex.Length)].ToString().ToUpper()); + input = Regex.Replace(input, @"\?a", m => _allChars[data.Random.Next(_allChars.Length)].ToString()); + input = Regex.Replace(input, @"\?m", m => _udChars[data.Random.Next(_udChars.Length)].ToString()); + input = Regex.Replace(input, @"\?n", m => _ldChars[data.Random.Next(_ldChars.Length)].ToString()); + input = Regex.Replace(input, @"\?i", m => _upperLowerDigits[data.Random.Next(_upperLowerDigits.Length)].ToString()); + input = Regex.Replace(input, @"\?f", m => _upperLower[data.Random.Next(_upperLower.Length)].ToString()); + input = Regex.Replace(input, @"\?c", m => customCharset[data.Random.Next(customCharset.Length)].ToString()); + data.Logger.Log($"Generated string: {input}", LogColors.YellowGreen); + return input; + } - [Block("Unescapes characters in a string")] - public static string Unescape(BotData data, [Variable] string input) - { - var unescaped = Regex.Unescape(input); - data.Logger.LogHeader(); - data.Logger.Log($"Unescaped: {unescaped}", LogColors.YellowGreen); - return unescaped; - } + [Block("Unescapes characters in a string")] + public static string Unescape(BotData data, [Variable] string input) + { + data.Logger.LogHeader(); + + var unescaped = Regex.Unescape(input); + data.Logger.Log($"Unescaped: {unescaped}", LogColors.YellowGreen); + return unescaped; + } - [Block("Splits a string into a list")] - public static List Split(BotData data, [Variable] string input, string separator) - { - var split = input.Split(separator, StringSplitOptions.None).ToList(); - data.Logger.LogHeader(); - data.Logger.Log($"Split the string into {split.Count}", LogColors.YellowGreen); - return split; - } + [Block("Splits a string into a list")] + public static List Split(BotData data, [Variable] string input, string separator) + { + data.Logger.LogHeader(); + + var split = input.Split(separator).ToList(); + data.Logger.Log($"Split the string into {split.Count}", LogColors.YellowGreen); + return split; + } - [Block("Gets the character at a specific index")] - public static string CharAt(BotData data, [Variable] string input, int index) - { - var character = input[index].ToString(); - data.Logger.LogHeader(); - data.Logger.Log($"The character at index {index} is {character}", LogColors.YellowGreen); - return character; - } + [Block("Gets the character at a specific index")] + public static string CharAt(BotData data, [Variable] string input, int index) + { + data.Logger.LogHeader(); + + var character = input[index].ToString(); + data.Logger.Log($"The character at index {index} is {character}", LogColors.YellowGreen); + return character; } } diff --git a/RuriLib/Blocks/Functions/Time/Methods.cs b/RuriLib/Blocks/Functions/Time/Methods.cs index a5815a511..28788c919 100644 --- a/RuriLib/Blocks/Functions/Time/Methods.cs +++ b/RuriLib/Blocks/Functions/Time/Methods.cs @@ -3,46 +3,61 @@ using RuriLib.Models.Bots; using System; -namespace RuriLib.Blocks.Functions.Time +namespace RuriLib.Blocks.Functions.Time; + +[BlockCategory("Time", "Blocks for working with dates and times", "#9acd32")] +public static class Methods { - [BlockCategory("Time", "Blocks for working with dates and times", "#9acd32")] - public static class Methods + /// + /// Gets the current unix time in seconds. + /// + [Block("Gets the current unix time in seconds")] + public static int CurrentUnixTime(BotData data, bool useUtc = false) { - [Block("Gets the current unix time in seconds")] - public static int CurrentUnixTime(BotData data, bool useUtc = false) - { - var dateTime = useUtc ? DateTime.UtcNow : DateTime.Now; - var time = (int)dateTime.ToUnixTime(); - data.Logger.LogHeader(); - data.Logger.Log($"Current unix time: {time}"); - return time; - } + data.Logger.LogHeader(); + + var dateTime = useUtc ? DateTime.UtcNow : DateTime.Now; + var time = (int)dateTime.ToUnixTime(); + data.Logger.Log($"Current unix time: {time}"); + return time; + } - [Block("Converts a unix time to a formatted datetime string")] - public static string UnixTimeToDate(BotData data, [Variable] int unixTime, string format = "yyyy-MM-dd:HH-mm-ss") - { - var date = ((long)unixTime).ToDateTimeUtc().ToString(format); - data.Logger.LogHeader(); - data.Logger.Log($"Formatted datetime: {date}"); - return date; - } + /// + /// Converts a unix time to a formatted datetime string. + /// + [Block("Converts a unix time to a formatted datetime string")] + public static string UnixTimeToDate(BotData data, [Variable] int unixTime, string format = "yyyy-MM-dd:HH-mm-ss") + { + data.Logger.LogHeader(); + + var date = ((long)unixTime).ToDateTimeUtc().ToString(format); + data.Logger.Log($"Formatted datetime: {date}"); + return date; + } - [Block("Parses a unix time from a formatted datetime string")] - public static int DateToUnixTime(BotData data, [Variable] string datetime, string format) - { - var time = (int)datetime.ToDateTime(format).ToUnixTime(); - data.Logger.LogHeader(); - data.Logger.Log($"Unix time: {time}"); - return time; - } + /// + /// Parses a unix time from a formatted datetime string. + /// + [Block("Parses a unix time from a formatted datetime string")] + public static int DateToUnixTime(BotData data, [Variable] string datetime, string format) + { + data.Logger.LogHeader(); + + var time = (int)datetime.ToDateTime(format).ToUnixTime(); + data.Logger.Log($"Unix time: {time}"); + return time; + } - [Block("Converts a unix time to an ISO8601 datetime string")] - public static string UnixTimeToISO8601(BotData data, [Variable] int unixTime) - { - var iso = ((long)unixTime).ToDateTimeUtc().ToISO8601(); - data.Logger.LogHeader(); - data.Logger.Log($"ISO8601 datetime: {iso}"); - return iso; - } + /// + /// Converts a unix time to an ISO8601 datetime string. + /// + [Block("Converts a unix time to an ISO8601 datetime string")] + public static string UnixTimeToISO8601(BotData data, [Variable] int unixTime) + { + data.Logger.LogHeader(); + + var iso = ((long)unixTime).ToDateTimeUtc().ToISO8601(); + data.Logger.Log($"ISO8601 datetime: {iso}"); + return iso; } } diff --git a/RuriLib/Blocks/Interop/Methods.cs b/RuriLib/Blocks/Interop/Methods.cs index ce804ea8b..ae2d32071 100644 --- a/RuriLib/Blocks/Interop/Methods.cs +++ b/RuriLib/Blocks/Interop/Methods.cs @@ -6,79 +6,74 @@ using RuriLib.Models.Bots; using System.Diagnostics; using System.IO; -using System.Linq; using System.Threading.Tasks; +using RuriLib.Exceptions; -namespace RuriLib.Blocks.Interop +namespace RuriLib.Blocks.Interop; + +[BlockCategory("Interop", "Blocks for interoperability with other programs", "#ddadaf")] +public static class Methods { - [BlockCategory("Interop", "Blocks for interoperability with other programs", "#ddadaf")] - public static class Methods + /// + /// Executes a shell command and redirects all stdout to the output variable. + /// + [Block("Executes a shell command and redirects all stdout to the output variable")] + public static string ShellCommand(BotData data, string executable, string arguments) { - [Block("Executes a shell command and redirects all stdout to the output variable")] - public static string ShellCommand(BotData data, string executable, string arguments) + data.Logger.LogHeader(); + + // For example executable is C:\Python27\python.exe and arguments is C:\sample_script.py + var start = new ProcessStartInfo(executable, arguments) { - // For example executable is C:\Python27\python.exe and arguments is C:\sample_script.py - var start = new ProcessStartInfo(executable, arguments) - { - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - data.Logger.LogHeader(); - - using var process = Process.Start(start); - using var reader = process.StandardOutput; + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; - var result = reader.ReadToEnd(); - data.Logger.Log($"Standard Output:", LogColors.PaleChestnut); - data.Logger.Log(result, LogColors.PaleChestnut); - return result; + using var process = Process.Start(start); + + if (process is null) + { + data.Logger.Log("The process could not be started", LogColors.PaleChestnut); + return string.Empty; } + + using var reader = process.StandardOutput; - /* - * These are not blocks, but they take BotData as an input. The ScriptBlockInstance will take care - * of writing C# code that calls these methods where necessary once it's transpiled. - */ - public static async Task InvokeNode(BotData data, string scriptOrFile, object[] parameters, bool isScript = false, string scriptHash = null) - { - data.Logger.LogHeader(); - T result; + var result = reader.ReadToEnd(); + data.Logger.Log($"Standard Output:", LogColors.PaleChestnut); + data.Logger.Log(result, LogColors.PaleChestnut); + return result; + } - if (isScript) - { - result = await InvokeFromStringOrCache(data, scriptOrFile, parameters, scriptHash); - } - else - { - result = await InvokeFromFile(data, scriptOrFile, parameters); - } + /* + * These are not blocks, but they take BotData as an input. The ScriptBlockInstance will take care + * of writing C# code that calls these methods where necessary once it's transpiled. + */ + public static async Task InvokeNode(BotData data, string scriptOrFile, object[] parameters, bool isScript = false, string? scriptHash = null) + { + data.Logger.LogHeader(); + + T result; - data.Logger.Log($"Executed NodeJS script with result: {result}", LogColors.PaleChestnut); - return result; + if (isScript) + { + result = await InvokeFromStringOrCache(data, scriptOrFile, parameters, scriptHash); } - - private static async Task InvokeFromStringOrCache(BotData data, string script, object[] parameters, string scriptHash = null) + else { - if (string.IsNullOrEmpty(scriptHash)) - { - return await StaticNodeJSService.InvokeFromStringAsync( - script, - scriptHash, - null, - parameters, - data.CancellationToken - ).ConfigureAwait(false); - } + result = await InvokeFromFile(data, scriptOrFile, parameters); + } - var (isCached, cachedResult) = await StaticNodeJSService.TryInvokeFromCacheAsync( - scriptHash, - null, - parameters, - data.CancellationToken - ).ConfigureAwait(false); + data.Logger.Log($"Executed NodeJS script with result: {result}", LogColors.PaleChestnut); + return result; + } - return isCached ? cachedResult : await StaticNodeJSService.InvokeFromStringAsync( + private static async Task InvokeFromStringOrCache(BotData data, string script, object[] parameters, string? scriptHash = null) + { + if (string.IsNullOrEmpty(scriptHash)) + { + return await StaticNodeJSService.InvokeFromStringAsync( script, scriptHash, null, @@ -87,39 +82,68 @@ private static async Task InvokeFromStringOrCache(BotData data, string scr ).ConfigureAwait(false); } - private static async Task InvokeFromFile(BotData data, string filePath, object[] parameters) - { - return await StaticNodeJSService.InvokeFromFileAsync( - filePath, - null, - parameters, - data.CancellationToken - ).ConfigureAwait(false); - } + var (isCached, cachedResult) = await StaticNodeJSService.TryInvokeFromCacheAsync( + scriptHash, + null, + parameters, + data.CancellationToken + ).ConfigureAwait(false); - public static Engine InvokeJint(BotData data, Engine engine, string scriptFile) - { - data.Logger.LogHeader(); - var script = File.ReadAllText(scriptFile); - var completionValue = engine.Evaluate(script); - data.Logger.Log($"Executed Jint script with completion value: {completionValue}", LogColors.PaleChestnut); - return engine; - } + return isCached ? cachedResult : await StaticNodeJSService.InvokeFromStringAsync( + script, + scriptHash, + null, + parameters, + data.CancellationToken + ).ConfigureAwait(false); + } - public static ScriptScope GetIronPyScope(BotData data) + private static async Task InvokeFromFile(BotData data, string filePath, object[] parameters) + { + return await StaticNodeJSService.InvokeFromFileAsync( + filePath, + null, + parameters, + data.CancellationToken + ).ConfigureAwait(false); + } + + public static Engine InvokeJint(BotData data, Engine engine, string scriptFile) + { + data.Logger.LogHeader(); + + var script = File.ReadAllText(scriptFile); + var completionValue = engine.Evaluate(script); + data.Logger.Log($"Executed Jint script with completion value: {completionValue}", LogColors.PaleChestnut); + return engine; + } + + public static ScriptScope GetIronPyScope(BotData data) + { + data.Logger.LogHeader(); + + data.Logger.Log("Getting a new IronPython scope.", LogColors.PaleChestnut); + var engine = data.TryGetObject("ironPyEngine"); + + if (engine is null) { - data.Logger.LogHeader(); - data.Logger.Log($"Getting a new IronPython scope.", LogColors.PaleChestnut); - var engine = data.TryGetObject("ironPyEngine"); - return engine.CreateScope(); + throw new BlockExecutionException("The IronPython engine is not initialized"); } + + return engine.CreateScope(); + } - public static void ExecuteIronPyScript(BotData data, ScriptScope scope, string scriptFile) + public static void ExecuteIronPyScript(BotData data, ScriptScope scope, string scriptFile) + { + var engine = data.TryGetObject("ironPyEngine"); + + if (engine is null) { - var engine = data.TryGetObject("ironPyEngine"); - var code = engine.CreateScriptSourceFromFile(scriptFile); - var result = code.Execute(scope); - data.Logger.Log($"Executed IronPython script with result {result}", LogColors.PaleChestnut); + throw new BlockExecutionException("The IronPython engine is not initialized"); } + + var code = engine.CreateScriptSourceFromFile(scriptFile); + var result = code.Execute(scope); + data.Logger.Log($"Executed IronPython script with result {result}", LogColors.PaleChestnut); } } diff --git a/RuriLib/Blocks/Parsing/Methods.cs b/RuriLib/Blocks/Parsing/Methods.cs index bf485e1e6..5e39a78a7 100644 --- a/RuriLib/Blocks/Parsing/Methods.cs +++ b/RuriLib/Blocks/Parsing/Methods.cs @@ -7,173 +7,182 @@ using System.Linq; using System.Text.RegularExpressions; -namespace RuriLib.Blocks.Parsing +namespace RuriLib.Blocks.Parsing; + +[BlockCategory("Parsing", "Blocks for extracting data from strings", "#ffd700")] +public static class Methods { - [BlockCategory("Parsing", "Blocks for extracting data from strings", "#ffd700")] - public static class Methods + #region LR + public static List ParseBetweenStringsRecursive(BotData data, string input, + string leftDelim, string rightDelim, bool caseSensitive = true, string prefix = "", string suffix = "", + bool urlEncodeOutput = false) { - #region LR - public static List ParseBetweenStringsRecursive(BotData data, string input, - string leftDelim, string rightDelim, bool caseSensitive = true, string prefix = "", string suffix = "", - bool urlEncodeOutput = false) - { - var parsed = LRParser.ParseBetween(input, leftDelim, rightDelim, caseSensitive) - .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + data.Logger.LogHeader(); + + var parsed = LRParser.ParseBetween(input, leftDelim, rightDelim, caseSensitive) + .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + + data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); + data.Logger.Log(parsed, LogColors.Yellow); + return parsed; + } - data.Logger.LogHeader(); - data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); - data.Logger.Log(parsed, LogColors.Yellow); - return parsed; - } + public static string ParseBetweenStrings(BotData data, string input, + string leftDelim, string rightDelim, bool caseSensitive = true, string prefix = "", string suffix = "", + bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = LRParser.ParseBetween(input, leftDelim, rightDelim, caseSensitive).FirstOrDefault() ?? string.Empty; + parsed = prefix + parsed + suffix; - public static string ParseBetweenStrings(BotData data, string input, - string leftDelim, string rightDelim, bool caseSensitive = true, string prefix = "", string suffix = "", - bool urlEncodeOutput = false) + if (urlEncodeOutput) { - var parsed = LRParser.ParseBetween(input, leftDelim, rightDelim, caseSensitive).FirstOrDefault() ?? string.Empty; - parsed = prefix + parsed + suffix; + parsed = Uri.EscapeDataString(parsed); + } - if (urlEncodeOutput) - { - parsed = Uri.EscapeDataString(parsed); - } + data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); + return parsed; + } + #endregion - data.Logger.LogHeader(); - data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); - return parsed; - } - #endregion + #region HTML + public static List QueryCssSelectorRecursive(BotData data, string htmlPage, + string cssSelector, string attributeName, string prefix = "", string suffix = "", bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = HtmlParser.QueryAttributeAll(htmlPage, cssSelector, attributeName) + .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + + data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); + data.Logger.Log(parsed, LogColors.Yellow); + return parsed; + } - #region HTML - public static List QueryCssSelectorRecursive(BotData data, string htmlPage, - string cssSelector, string attributeName, string prefix = "", string suffix = "", bool urlEncodeOutput = false) - { - var parsed = HtmlParser.QueryAttributeAll(htmlPage, cssSelector, attributeName) - .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + public static string QueryCssSelector(BotData data, string htmlPage, string cssSelector, string attributeName, + string prefix = "", string suffix = "", bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = HtmlParser.QueryAttributeAll(htmlPage, cssSelector, attributeName).FirstOrDefault() ?? string.Empty; + parsed = prefix + parsed + suffix; - data.Logger.LogHeader(); - data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); - data.Logger.Log(parsed, LogColors.Yellow); - return parsed; + if (urlEncodeOutput) + { + parsed = Uri.EscapeDataString(parsed); } - public static string QueryCssSelector(BotData data, string htmlPage, string cssSelector, string attributeName, - string prefix = "", string suffix = "", bool urlEncodeOutput = false) - { - var parsed = HtmlParser.QueryAttributeAll(htmlPage, cssSelector, attributeName).FirstOrDefault() ?? string.Empty; - parsed = prefix + parsed + suffix; + data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); - if (urlEncodeOutput) - { - parsed = Uri.EscapeDataString(parsed); - } + return parsed; + } + #endregion - data.Logger.LogHeader(); - data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); + #region XML + public static List QueryXPathRecursive(BotData data, string xmlPage, + string xPath, string attributeName, string prefix = "", string suffix = "", bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = HtmlParser.QueryXPathAll(xmlPage, xPath, attributeName) + .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + + data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); + data.Logger.Log(parsed, LogColors.Yellow); + return parsed; + } - return parsed; - } - #endregion + public static string QueryXPath(BotData data, string xmlPage, string xPath, string attributeName, + string prefix = "", string suffix = "", bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = HtmlParser.QueryXPathAll(xmlPage, xPath, attributeName).FirstOrDefault() ?? string.Empty; + parsed = prefix + parsed + suffix; - #region XML - public static List QueryXPathRecursive(BotData data, string xmlPage, - string xPath, string attributeName, string prefix = "", string suffix = "", bool urlEncodeOutput = false) + if (urlEncodeOutput) { - var parsed = HtmlParser.QueryXPathAll(xmlPage, xPath, attributeName) - .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); - - data.Logger.LogHeader(); - data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); - data.Logger.Log(parsed, LogColors.Yellow); - return parsed; + parsed = Uri.EscapeDataString(parsed); } - public static string QueryXPath(BotData data, string xmlPage, string xPath, string attributeName, - string prefix = "", string suffix = "", bool urlEncodeOutput = false) - { - var parsed = HtmlParser.QueryXPathAll(xmlPage, xPath, attributeName).FirstOrDefault() ?? string.Empty; - parsed = prefix + parsed + suffix; + data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); - if (urlEncodeOutput) - { - parsed = Uri.EscapeDataString(parsed); - } + return parsed; + } + #endregion - data.Logger.LogHeader(); - data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); + #region JSON + public static List QueryJsonTokenRecursive(BotData data, string json, string jToken, string prefix = "", + string suffix = "", bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = JsonParser.GetValuesByKey(json, jToken) + .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + + data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); + data.Logger.Log(parsed, LogColors.Yellow); + return parsed; + } - return parsed; - } - #endregion + public static string QueryJsonToken(BotData data, string json, string jToken, string prefix = "", string suffix = "", + bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = JsonParser.GetValuesByKey(json, jToken).FirstOrDefault() ?? string.Empty; + parsed = prefix + parsed + suffix; - #region JSON - public static List QueryJsonTokenRecursive(BotData data, string json, string jToken, string prefix = "", - string suffix = "", bool urlEncodeOutput = false) + if (urlEncodeOutput) { - var parsed = JsonParser.GetValuesByKey(json, jToken) - .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); - - data.Logger.LogHeader(); - data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); - data.Logger.Log(parsed, LogColors.Yellow); - return parsed; + parsed = Uri.EscapeDataString(parsed); } - public static string QueryJsonToken(BotData data, string json, string jToken, string prefix = "", string suffix = "", - bool urlEncodeOutput = false) - { - var parsed = JsonParser.GetValuesByKey(json, jToken).FirstOrDefault() ?? string.Empty; - parsed = prefix + parsed + suffix; - - if (urlEncodeOutput) - { - parsed = Uri.EscapeDataString(parsed); - } - - data.Logger.LogHeader(); - data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); - return parsed; - } - #endregion + data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); + return parsed; + } + #endregion - #region REGEX - public static List MatchRegexGroupsRecursive(BotData data, string input, - string pattern, string outputFormat, bool multiLine, string prefix = "", string suffix = "", bool urlEncodeOutput = false) - { - var parsed = RegexParser.MatchGroupsToString(input, pattern, outputFormat, multiLine ? RegexOptions.Multiline : RegexOptions.None) - .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + #region REGEX + public static List MatchRegexGroupsRecursive(BotData data, string input, + string pattern, string outputFormat, bool multiLine, string prefix = "", string suffix = "", bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = RegexParser.MatchGroupsToString(input, pattern, outputFormat, multiLine ? RegexOptions.Multiline : RegexOptions.None) + .Select(p => prefix + p + suffix).Select(p => urlEncodeOutput ? Uri.UnescapeDataString(p) : p).ToList(); + + data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); + data.Logger.Log(parsed, LogColors.Yellow); + return parsed; + } - data.Logger.LogHeader(); - data.Logger.Log($"Parsed {parsed.Count} values:", LogColors.Yellow); - data.Logger.Log(parsed, LogColors.Yellow); - return parsed; - } + // Old signature (without multiLine) for backwards compatibility + public static List MatchRegexGroupsRecursive(BotData data, string input, + string pattern, string outputFormat, string prefix = "", string suffix = "", bool urlEncodeOutput = false) + => MatchRegexGroupsRecursive(data, input, pattern, outputFormat, false, prefix, suffix, urlEncodeOutput); - // Old signature (without multiLine) for backwards compatibility - public static List MatchRegexGroupsRecursive(BotData data, string input, - string pattern, string outputFormat, string prefix = "", string suffix = "", bool urlEncodeOutput = false) - => MatchRegexGroupsRecursive(data, input, pattern, outputFormat, false, prefix, suffix, urlEncodeOutput); + public static string MatchRegexGroups(BotData data, string input, string pattern, string outputFormat, + bool multiLine, string prefix = "", string suffix = "", bool urlEncodeOutput = false) + { + data.Logger.LogHeader(); + + var parsed = RegexParser.MatchGroupsToString(input, pattern, outputFormat, multiLine ? RegexOptions.Multiline : RegexOptions.None).FirstOrDefault() ?? string.Empty; + parsed = prefix + parsed + suffix; - public static string MatchRegexGroups(BotData data, string input, string pattern, string outputFormat, - bool multiLine, string prefix = "", string suffix = "", bool urlEncodeOutput = false) + if (urlEncodeOutput) { - var parsed = RegexParser.MatchGroupsToString(input, pattern, outputFormat, multiLine ? RegexOptions.Multiline : RegexOptions.None).FirstOrDefault() ?? string.Empty; - parsed = prefix + parsed + suffix; - - if (urlEncodeOutput) - { - parsed = Uri.EscapeDataString(parsed); - } - - data.Logger.LogHeader(); - data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); - return parsed; + parsed = Uri.EscapeDataString(parsed); } - // Old signature (without multiLine) for backwards compatibility - public static string MatchRegexGroups(BotData data, string input, string pattern, string outputFormat, - string prefix = "", string suffix = "", bool urlEncodeOutput = false) - => MatchRegexGroups(data, input, pattern, outputFormat, false, prefix, suffix, urlEncodeOutput); - #endregion + data.Logger.Log($"Parsed value: {parsed}", LogColors.Yellow); + return parsed; } + + // Old signature (without multiLine) for backwards compatibility + public static string MatchRegexGroups(BotData data, string input, string pattern, string outputFormat, + string prefix = "", string suffix = "", bool urlEncodeOutput = false) + => MatchRegexGroups(data, input, pattern, outputFormat, false, prefix, suffix, urlEncodeOutput); + #endregion } diff --git a/RuriLib/Blocks/Puppeteer/Browser/Methods.cs b/RuriLib/Blocks/Puppeteer/Browser/Methods.cs index d1951cfc0..4d5767c3f 100644 --- a/RuriLib/Blocks/Puppeteer/Browser/Methods.cs +++ b/RuriLib/Blocks/Puppeteer/Browser/Methods.cs @@ -6,247 +6,249 @@ using RuriLib.Logging; using RuriLib.Models.Bots; using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using ProxyType = RuriLib.Models.Proxies.ProxyType; using RuriLib.Helpers; -namespace RuriLib.Blocks.Puppeteer.Browser +namespace RuriLib.Blocks.Puppeteer.Browser; + +[BlockCategory("Browser", "Blocks for interacting with a puppeteer browser", "#e9967a")] +public static class Methods { - [BlockCategory("Browser", "Blocks for interacting with a puppeteer browser", "#e9967a")] - public static class Methods + [Block("Opens a new puppeteer browser", name = "Open Browser")] + public static async Task PuppeteerOpenBrowser(BotData data, string extraCmdLineArgs = "") { - [Block("Opens a new puppeteer browser", name = "Open Browser")] - public static async Task PuppeteerOpenBrowser(BotData data, string extraCmdLineArgs = "") + data.Logger.LogHeader(); + + // Check if there is already an open browser + var oldBrowser = data.TryGetObject("puppeteer"); + if (oldBrowser is not null && !oldBrowser.IsClosed) { - data.Logger.LogHeader(); + data.Logger.Log("The browser is already open, close it if you want to open a new browser", LogColors.DarkSalmon); + return; + } - // Check if there is already an open browser - var oldBrowser = data.TryGetObject("puppeteer"); - if (oldBrowser is not null && !oldBrowser.IsClosed) - { - data.Logger.Log("The browser is already open, close it if you want to open a new browser", LogColors.DarkSalmon); - return; - } + var args = data.ConfigSettings.BrowserSettings.CommandLineArgs; - var args = data.ConfigSettings.BrowserSettings.CommandLineArgs; + // Extra command line args (to have dynamic args via variables) + if (!string.IsNullOrWhiteSpace(extraCmdLineArgs)) + { + args += ' ' + extraCmdLineArgs; + } - // Extra command line args (to have dynamic args via variables) - if (!string.IsNullOrWhiteSpace(extraCmdLineArgs)) - { - args += ' ' + extraCmdLineArgs; - } + // If it's running in docker, currently it runs under root, so add the --no-sandbox otherwise chrome won't work + if (Utils.IsDocker()) + { + args += " --no-sandbox"; + } - // If it's running in docker, currently it runs under root, so add the --no-sandbox otherwise chrome won't work - if (Utils.IsDocker()) + if (data.Proxy != null && data.UseProxy) + { + if (data.Proxy.Type == ProxyType.Http || !data.Proxy.NeedsAuthentication) { - args += " --no-sandbox"; + args += $" --proxy-server={data.Proxy.Type.ToString().ToLower()}://{data.Proxy.Host}:{data.Proxy.Port}"; } - - if (data.Proxy != null && data.UseProxy) + else { - if (data.Proxy.Type == ProxyType.Http || !data.Proxy.NeedsAuthentication) - { - args += $" --proxy-server={data.Proxy.Type.ToString().ToLower()}://{data.Proxy.Host}:{data.Proxy.Port}"; - } - else - { - var proxyType = data.Proxy.Type == ProxyType.Socks5 ? Yove.Proxy.ProxyType.Socks5 : Yove.Proxy.ProxyType.Socks4; - var proxyClient = new ProxyClient( - data.Proxy.Host, data.Proxy.Port, - data.Proxy.Username, data.Proxy.Password, - proxyType); - data.SetObject("puppeteer.yoveproxy", proxyClient); - args += $" --proxy-server={proxyClient.GetProxy(null).Authority}"; - } + var proxyType = data.Proxy.Type == ProxyType.Socks5 ? Yove.Proxy.ProxyType.Socks5 : Yove.Proxy.ProxyType.Socks4; + var proxyClient = new ProxyClient( + data.Proxy.Host, data.Proxy.Port, + data.Proxy.Username, data.Proxy.Password, + proxyType); + data.SetObject("puppeteer.yoveproxy", proxyClient); + args += $" --proxy-server={proxyClient.GetProxy(null!)!.Authority}"; } - - // Configure the options - var launchOptions = new LaunchOptions - { - Args = new string[] { args }, - ExecutablePath = data.Providers.PuppeteerBrowser.ChromeBinaryLocation, - IgnoredDefaultArgs = new string[] { "--disable-extensions", "--enable-automation" }, - Headless = data.ConfigSettings.BrowserSettings.Headless, - DefaultViewport = null // This is important - }; - - // Add the plugins - var extra = new PuppeteerExtra(); - extra.Use(new StealthPlugin()); - - // Launch the browser - var browser = await extra.LaunchAsync(launchOptions); - browser.IgnoreHTTPSErrors = data.ConfigSettings.BrowserSettings.IgnoreHttpsErrors; - - // Save the browser for further use - data.SetObject("puppeteer", browser); - var page = (await browser.PagesAsync()).First(); - SetPageAndFrame(data, page); - await SetPageLoadingOptions(data, page); - - // Authenticate if the proxy requires auth - if (data.UseProxy && data.Proxy is { NeedsAuthentication: true, Type: ProxyType.Http } proxy) - await page.AuthenticateAsync(new Credentials { Username = proxy.Username, Password = proxy.Password }); - - data.Logger.Log($"{(launchOptions.Headless ? "Headless " : "")}Browser opened successfully!", LogColors.DarkSalmon); } - [Block("Closes an open puppeteer browser", name = "Close Browser")] - public static async Task PuppeteerCloseBrowser(BotData data) + // Configure the options + var launchOptions = new LaunchOptions { - data.Logger.LogHeader(); - - var browser = GetBrowser(data); - await browser.CloseAsync(); - StopYoveProxyInternalServer(data); - data.Logger.Log("Browser closed successfully!", LogColors.DarkSalmon); + Args = [args], + ExecutablePath = data.Providers.PuppeteerBrowser.ChromeBinaryLocation, + IgnoredDefaultArgs = ["--disable-extensions", "--enable-automation"], + Headless = data.ConfigSettings.BrowserSettings.Headless, + DefaultViewport = null // This is important + }; + + // Add the plugins + var extra = new PuppeteerExtra(); + extra.Use(new StealthPlugin()); + + // Launch the browser + var browser = await extra.LaunchAsync(launchOptions); + browser.IgnoreHTTPSErrors = data.ConfigSettings.BrowserSettings.IgnoreHttpsErrors; + + // Save the browser for further use + data.SetObject("puppeteer", browser); + var page = (await browser.PagesAsync()).First(); + SetPageAndFrame(data, page); + await SetPageLoadingOptions(data, page); + + // Authenticate if the proxy requires auth + if (data is { UseProxy: true, Proxy: { NeedsAuthentication: true, Type: ProxyType.Http } proxy }) + { + await page.AuthenticateAsync(new Credentials { Username = proxy.Username, Password = proxy.Password }); } - [Block("Opens a new page in a new browser tab", name = "New Tab")] - public static async Task PuppeteerNewTab(BotData data) - { - data.Logger.LogHeader(); + data.Logger.Log($"{(launchOptions.Headless ? "Headless " : "")}Browser opened successfully!", LogColors.DarkSalmon); + } - var browser = GetBrowser(data); - var page = await browser.NewPageAsync(); - await SetPageLoadingOptions(data, page); + [Block("Closes an open puppeteer browser", name = "Close Browser")] + public static async Task PuppeteerCloseBrowser(BotData data) + { + data.Logger.LogHeader(); - SetPageAndFrame(data, page); // Set the new page as active - data.Logger.Log($"Opened a new page", LogColors.DarkSalmon); - } + var browser = GetBrowser(data); + await browser.CloseAsync(); + StopYoveProxyInternalServer(data); + data.Logger.Log("Browser closed successfully!", LogColors.DarkSalmon); + } - [Block("Closes the currently active browser tab", name = "Close Tab")] - public static async Task PuppeteerCloseTab(BotData data) - { - data.Logger.LogHeader(); + [Block("Opens a new page in a new browser tab", name = "New Tab")] + public static async Task PuppeteerNewTab(BotData data) + { + data.Logger.LogHeader(); - var browser = GetBrowser(data); - var page = GetPage(data); + var browser = GetBrowser(data); + var page = await browser.NewPageAsync(); + await SetPageLoadingOptions(data, page); + + SetPageAndFrame(data, page); // Set the new page as active + data.Logger.Log("Opened a new page", LogColors.DarkSalmon); + } + + [Block("Closes the currently active browser tab", name = "Close Tab")] + public static async Task PuppeteerCloseTab(BotData data) + { + data.Logger.LogHeader(); + + var browser = GetBrowser(data); + var page = GetPage(data); - // Close the page - await page.CloseAsync(); + // Close the page + await page.CloseAsync(); - // Set the first page as active - page = (await browser.PagesAsync()).FirstOrDefault(); - SetPageAndFrame(data, page); - - if (page != null) - await page.BringToFrontAsync(); + // Set the first page as active + page = (await browser.PagesAsync()).FirstOrDefault(); + SetPageAndFrame(data, page); - data.Logger.Log($"Closed the active page", LogColors.DarkSalmon); + if (page != null) + { + await page.BringToFrontAsync(); } - [Block("Switches to the browser tab with a specified index", name = "Switch to Tab")] - public static async Task PuppeteerSwitchToTab(BotData data, int index) - { - data.Logger.LogHeader(); + data.Logger.Log("Closed the active page", LogColors.DarkSalmon); + } - var browser = GetBrowser(data); + [Block("Switches to the browser tab with a specified index", name = "Switch to Tab")] + public static async Task PuppeteerSwitchToTab(BotData data, int index) + { + data.Logger.LogHeader(); + + var browser = GetBrowser(data); - // Workaround https://github.com/hardkoded/puppeteer-sharp/issues/1587 - await browser.GetVersionAsync(); + // Workaround https://github.com/hardkoded/puppeteer-sharp/issues/1587 + await browser.GetVersionAsync(); - var pages = await browser.PagesAsync(); - var page = pages[index]; + var pages = await browser.PagesAsync(); + var page = pages[index]; - await page.BringToFrontAsync(); - SetPageAndFrame(data, page); + await page.BringToFrontAsync(); + SetPageAndFrame(data, page); - data.Logger.Log($"Switched to tab with index {index}", LogColors.DarkSalmon); - } + data.Logger.Log($"Switched to tab with index {index}", LogColors.DarkSalmon); + } - [Block("Reloads the current page", name = "Reload")] - public static async Task PuppeteerReload(BotData data) - { - data.Logger.LogHeader(); + [Block("Reloads the current page", name = "Reload")] + public static async Task PuppeteerReload(BotData data) + { + data.Logger.LogHeader(); - var page = GetPage(data); - await page.ReloadAsync(); - SwitchToMainFramePrivate(data); + var page = GetPage(data); + await page.ReloadAsync(); + SwitchToMainFramePrivate(data); - data.Logger.Log($"Reloaded the page", LogColors.DarkSalmon); - } + data.Logger.Log("Reloaded the page", LogColors.DarkSalmon); + } - [Block("Goes back to the previously visited page", name = "Go Back")] - public static async Task PuppeteerGoBack(BotData data) - { - data.Logger.LogHeader(); + [Block("Goes back to the previously visited page", name = "Go Back")] + public static async Task PuppeteerGoBack(BotData data) + { + data.Logger.LogHeader(); - var page = GetPage(data); - await page.GoBackAsync(); - SwitchToMainFramePrivate(data); + var page = GetPage(data); + await page.GoBackAsync(); + SwitchToMainFramePrivate(data); - data.Logger.Log($"Went back to the previously visited page", LogColors.DarkSalmon); - } + data.Logger.Log("Went back to the previously visited page", LogColors.DarkSalmon); + } - [Block("Goes forward to the next visited page", name = "Go Forward")] - public static async Task PuppeteerGoForward(BotData data) - { - data.Logger.LogHeader(); + [Block("Goes forward to the next visited page", name = "Go Forward")] + public static async Task PuppeteerGoForward(BotData data) + { + data.Logger.LogHeader(); - var page = GetPage(data); - await page.GoForwardAsync(); - SwitchToMainFramePrivate(data); + var page = GetPage(data); + await page.GoForwardAsync(); + SwitchToMainFramePrivate(data); - data.Logger.Log($"Went forward to the next visited page", LogColors.DarkSalmon); - } + data.Logger.Log("Went forward to the next visited page", LogColors.DarkSalmon); + } - private static IBrowser GetBrowser(BotData data) - => data.TryGetObject("puppeteer") ?? throw new Exception("The browser is not open!"); + private static IBrowser GetBrowser(BotData data) + => data.TryGetObject("puppeteer") ?? throw new Exception("The browser is not open!"); - private static IPage GetPage(BotData data) - => data.TryGetObject("puppeteerPage") ?? throw new Exception("No pages open!"); + private static IPage GetPage(BotData data) + => data.TryGetObject("puppeteerPage") ?? throw new Exception("No pages open!"); - private static void SwitchToMainFramePrivate(BotData data) - => data.SetObject("puppeteerFrame", GetPage(data).MainFrame); + private static void SwitchToMainFramePrivate(BotData data) + => data.SetObject("puppeteerFrame", GetPage(data).MainFrame); - private static void SetPageAndFrame(BotData data, IPage page) - { - data.SetObject("puppeteerPage", page, false); - SwitchToMainFramePrivate(data); - } + private static void SetPageAndFrame(BotData data, IPage? page) + { + data.SetObject("puppeteerPage", page, false); + SwitchToMainFramePrivate(data); + } - private static void StopYoveProxyInternalServer(BotData data) - => data.TryGetObject("puppeteer.yoveproxy")?.Dispose(); + private static void StopYoveProxyInternalServer(BotData data) + => data.TryGetObject("puppeteer.yoveproxy")?.Dispose(); - private static async Task SetPageLoadingOptions(BotData data, IPage page) + private static async Task SetPageLoadingOptions(BotData data, IPage page) + { + await page.SetRequestInterceptionAsync(true); + page.Request += (_, e) => { - await page.SetRequestInterceptionAsync(true); - page.Request += (sender, e) => + // If we only want documents and scripts but the resource is not one of those, block + if (data.ConfigSettings.BrowserSettings.LoadOnlyDocumentAndScript && + e.Request.ResourceType != ResourceType.Document && e.Request.ResourceType != ResourceType.Script) { - // If we only want documents and scripts but the resource is not one of those, block - if (data.ConfigSettings.BrowserSettings.LoadOnlyDocumentAndScript && - e.Request.ResourceType != ResourceType.Document && e.Request.ResourceType != ResourceType.Script) - { - e.Request.AbortAsync(); - } - - // If the url contains one of the blocked urls - else if (data.ConfigSettings.BrowserSettings.BlockedUrls - .Where(u => !string.IsNullOrWhiteSpace(u)) - .Any(u => e.Request.Url.Contains(u, StringComparison.OrdinalIgnoreCase))) - { - e.Request.AbortAsync(); - } - - // Otherwise all good, continue - else - { - e.Request.ContinueAsync(); - } - }; + e.Request.AbortAsync(); + } + + // If the url contains one of the blocked urls + else if (data.ConfigSettings.BrowserSettings.BlockedUrls + .Where(u => !string.IsNullOrWhiteSpace(u)) + .Any(u => e.Request.Url.Contains(u, StringComparison.OrdinalIgnoreCase))) + { + e.Request.AbortAsync(); + } - if (data.ConfigSettings.BrowserSettings.DismissDialogs) + // Otherwise all good, continue + else { - page.Dialog += (sender, e) => - { - data.Logger.Log($"Dialog automatically dismissed: {e.Dialog.Message}", LogColors.DarkSalmon); - e.Dialog.Dismiss(); - }; + e.Request.ContinueAsync(); } + }; + + if (data.ConfigSettings.BrowserSettings.DismissDialogs) + { + page.Dialog += (_, e) => + { + data.Logger.Log($"Dialog automatically dismissed: {e.Dialog.Message}", LogColors.DarkSalmon); + e.Dialog.Dismiss(); + }; } } } diff --git a/RuriLib/Blocks/Puppeteer/Elements/Methods.cs b/RuriLib/Blocks/Puppeteer/Elements/Methods.cs index 49e7c23da..e34cc3638 100644 --- a/RuriLib/Blocks/Puppeteer/Elements/Methods.cs +++ b/RuriLib/Blocks/Puppeteer/Elements/Methods.cs @@ -9,375 +9,374 @@ using System.Linq; using System.Threading.Tasks; -namespace RuriLib.Blocks.Puppeteer.Elements +namespace RuriLib.Blocks.Puppeteer.Elements; + +[BlockCategory("Elements", "Blocks for interacting with elements on a puppeteer browser page", "#e9967a")] +public static class Methods { - [BlockCategory("Elements", "Blocks for interacting with elements on a puppeteer browser page", "#e9967a")] - public static class Methods + [Block("Sets the value of the specified attribute of an element", name = "Set Attribute Value")] + public static async Task PuppeteerSetAttributeValue(BotData data, FindElementBy findBy, string identifier, int index, + string attributeName, string value) { - [Block("Sets the value of the specified attribute of an element", name = "Set Attribute Value")] - public static async Task PuppeteerSetAttributeValue(BotData data, FindElementBy findBy, string identifier, int index, - string attributeName, string value) - { - data.Logger.LogHeader(); + data.Logger.LogHeader(); - var elemScript = GetElementScript(findBy, identifier, index); - var frame = GetFrame(data); - var script = elemScript + $".setAttribute('{attributeName}', '{value}');"; - await frame.EvaluateExpressionAsync(script); + var elemScript = GetElementScript(findBy, identifier, index); + var frame = GetFrame(data); + var script = elemScript + $".setAttribute('{attributeName}', '{value}');"; + await frame.EvaluateExpressionAsync(script); - data.Logger.Log($"Set value {value} of attribute {attributeName} by executing {script}", LogColors.DarkSalmon); - } + data.Logger.Log($"Set value {value} of attribute {attributeName} by executing {script}", LogColors.DarkSalmon); + } - [Block("Types text in an input field", name = "Type")] - public static async Task PuppeteerTypeElement(BotData data, FindElementBy findBy, string identifier, int index, - string text, int timeBetweenKeystrokes = 0) - { - data.Logger.LogHeader(); + [Block("Types text in an input field", name = "Type")] + public static async Task PuppeteerTypeElement(BotData data, FindElementBy findBy, string identifier, int index, + string text, int timeBetweenKeystrokes = 0) + { + data.Logger.LogHeader(); - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - await elem.TypeAsync(text, new PuppeteerSharp.Input.TypeOptions { Delay = timeBetweenKeystrokes }); + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + await elem.TypeAsync(text, new PuppeteerSharp.Input.TypeOptions { Delay = timeBetweenKeystrokes }); - data.Logger.Log($"Typed {text}", LogColors.DarkSalmon); - } + data.Logger.Log($"Typed {text}", LogColors.DarkSalmon); + } + + [Block("Types text in an input field with human-like random delays", name = "Type Human")] + public static async Task PuppeteerTypeElementHuman(BotData data, FindElementBy findBy, string identifier, int index, + string text) + { + data.Logger.LogHeader(); - [Block("Types text in an input field with human-like random delays", name = "Type Human")] - public static async Task PuppeteerTypeElementHuman(BotData data, FindElementBy findBy, string identifier, int index, - string text) + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + + foreach (var c in text) { - data.Logger.LogHeader(); + await elem.TypeAsync(c.ToString()); + await Task.Delay(data.Random.Next(100, 300)); // Wait between 100 and 300 ms (average human type speed is 60 WPM ~ 360 CPM) + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); + data.Logger.Log($"Typed {text}", LogColors.DarkSalmon); + } - foreach (var c in text) - { - await elem.TypeAsync(c.ToString()); - await Task.Delay(data.Random.Next(100, 300)); // Wait between 100 and 300 ms (average human type speed is 60 WPM ~ 360 CPM) - } + [Block("Clicks an element", name = "Click")] + public static async Task PuppeteerClick(BotData data, FindElementBy findBy, string identifier, int index, + PuppeteerSharp.Input.MouseButton mouseButton = PuppeteerSharp.Input.MouseButton.Left, int clickCount = 1, + int timeBetweenClicks = 0) + { + data.Logger.LogHeader(); - data.Logger.Log($"Typed {text}", LogColors.DarkSalmon); - } + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + await elem.ClickAsync(new PuppeteerSharp.Input.ClickOptions { Button = mouseButton, ClickCount = clickCount, Delay = timeBetweenClicks }); - [Block("Clicks an element", name = "Click")] - public static async Task PuppeteerClick(BotData data, FindElementBy findBy, string identifier, int index, - PuppeteerSharp.Input.MouseButton mouseButton = PuppeteerSharp.Input.MouseButton.Left, int clickCount = 1, - int timeBetweenClicks = 0) - { - data.Logger.LogHeader(); + data.Logger.Log($"Clicked {clickCount} time(s) with {mouseButton} button", LogColors.DarkSalmon); + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - await elem.ClickAsync(new PuppeteerSharp.Input.ClickOptions { Button = mouseButton, ClickCount = clickCount, Delay = timeBetweenClicks }); + [Block("Submits a form", name = "Submit")] + public static async Task PuppeteerSubmit(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - data.Logger.Log($"Clicked {clickCount} time(s) with {mouseButton} button", LogColors.DarkSalmon); - } + var elemScript = GetElementScript(findBy, identifier, index); + var frame = GetFrame(data); + var script = elemScript + ".submit();"; + await frame.EvaluateExpressionAsync(script); - [Block("Submits a form", name = "Submit")] - public static async Task PuppeteerSubmit(BotData data, FindElementBy findBy, string identifier, int index) - { - data.Logger.LogHeader(); + data.Logger.Log($"Submitted the form by executing {script}", LogColors.DarkSalmon); + } - var elemScript = GetElementScript(findBy, identifier, index); - var frame = GetFrame(data); - var script = elemScript + ".submit();"; - await frame.EvaluateExpressionAsync(script); + [Block("Selects a value in a select element", name = "Select")] + public static async Task PuppeteerSelect(BotData data, FindElementBy findBy, string identifier, int index, string value) + { + data.Logger.LogHeader(); - data.Logger.Log($"Submitted the form by executing {script}", LogColors.DarkSalmon); - } + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + await elem.SelectAsync(value); - [Block("Selects a value in a select element", name = "Select")] - public static async Task PuppeteerSelect(BotData data, FindElementBy findBy, string identifier, int index, string value) - { - data.Logger.LogHeader(); + data.Logger.Log($"Selected value {value}", LogColors.DarkSalmon); + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - await elem.SelectAsync(value); + [Block("Selects a value by index in a select element", name = "Select by Index")] + public static async Task PuppeteerSelectByIndex(BotData data, FindElementBy findBy, string identifier, int index, int selectionIndex) + { + data.Logger.LogHeader(); - data.Logger.Log($"Selected value {value}", LogColors.DarkSalmon); - } + var frame = GetFrame(data); + var elemScript = GetElementScript(findBy, identifier, index); + var script = elemScript + $".getElementsByTagName('option')[{selectionIndex}].value;"; + var value = (await frame.EvaluateExpressionAsync(script)).ToString(); - [Block("Selects a value by index in a select element", name = "Select by Index")] - public static async Task PuppeteerSelectByIndex(BotData data, FindElementBy findBy, string identifier, int index, int selectionIndex) - { - data.Logger.LogHeader(); + var elem = await GetElement(frame, findBy, identifier, index); + await elem.SelectAsync(value); - var frame = GetFrame(data); - var elemScript = GetElementScript(findBy, identifier, index); - var script = elemScript + $".getElementsByTagName('option')[{selectionIndex}].value;"; - var value = (await frame.EvaluateExpressionAsync(script)).ToString(); + data.Logger.Log($"Selected value {value}", LogColors.DarkSalmon); + } - var elem = await GetElement(frame, findBy, identifier, index); - await elem.SelectAsync(value); + [Block("Selects a value by text in a select element", name = "Select by Text")] + public static async Task PuppeteerSelectByText(BotData data, FindElementBy findBy, string identifier, int index, string text) + { + data.Logger.LogHeader(); - data.Logger.Log($"Selected value {value}", LogColors.DarkSalmon); - } + var frame = GetFrame(data); + var elemScript = GetElementScript(findBy, identifier, index); + var script = $"el={elemScript};for(let i=0;i PuppeteerGetAttributeValue(BotData data, FindElementBy findBy, string identifier, int index, + string attributeName = "innerText") + { + data.Logger.LogHeader(); - data.Logger.Log($"Selected text {text}", LogColors.DarkSalmon); - } + var elemScript = GetElementScript(findBy, identifier, index); + var frame = GetFrame(data); + var script = $"{elemScript}.{attributeName};"; + var value = await frame.EvaluateExpressionAsync(script); - [Block("Gets the value of an attribute of an element", name = "Get Attribute Value")] - public static async Task PuppeteerGetAttributeValue(BotData data, FindElementBy findBy, string identifier, int index, - string attributeName = "innerText") - { - data.Logger.LogHeader(); + data.Logger.Log($"Got value {value} of attribute {attributeName} by executing {script}", LogColors.DarkSalmon); + return value; + } - var elemScript = GetElementScript(findBy, identifier, index); - var frame = GetFrame(data); - var script = $"{elemScript}.{attributeName};"; - var value = await frame.EvaluateExpressionAsync(script); + [Block("Gets the values of an attribute of multiple elements", name = "Get Attribute Value All")] + public static async Task> PuppeteerGetAttributeValueAll(BotData data, FindElementBy findBy, string identifier, + string attributeName = "innerText") + { + data.Logger.LogHeader(); - data.Logger.Log($"Got value {value} of attribute {attributeName} by executing {script}", LogColors.DarkSalmon); - return value; - } + var elemScript = GetElementsScript(findBy, identifier); + var frame = GetFrame(data); + var script = $"Array.prototype.slice.call({elemScript}).map((item) => item.{attributeName})"; + var values = await frame.EvaluateExpressionAsync(script); - [Block("Gets the values of an attribute of multiple elements", name = "Get Attribute Value All")] - public static async Task> PuppeteerGetAttributeValueAll(BotData data, FindElementBy findBy, string identifier, - string attributeName = "innerText") - { - data.Logger.LogHeader(); + data.Logger.Log($"Got {values.Length} values for attribute {attributeName} by executing {script}", LogColors.DarkSalmon); + return values.ToList(); + } - var elemScript = GetElementsScript(findBy, identifier); - var frame = GetFrame(data); - var script = $"Array.prototype.slice.call({elemScript}).map((item) => item.{attributeName})"; - var values = await frame.EvaluateExpressionAsync(script); + [Block("Checks if an element is currently being displayed on the page", name = "Is Displayed")] + public static async Task PuppeteerIsDisplayed(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - data.Logger.Log($"Got {values.Length} values for attribute {attributeName} by executing {script}", LogColors.DarkSalmon); - return values.ToList(); - } + var elemScript = GetElementScript(findBy, identifier, index); + var frame = GetFrame(data); + var script = $"window.getComputedStyle({elemScript}).display !== 'none';"; + var displayed = await frame.EvaluateExpressionAsync(script); - [Block("Checks if an element is currently being displayed on the page", name = "Is Displayed")] - public static async Task PuppeteerIsDisplayed(BotData data, FindElementBy findBy, string identifier, int index) - { - data.Logger.LogHeader(); + data.Logger.Log($"Found out the element is{(displayed ? "" : " not")} displayed by executing {script}", LogColors.DarkSalmon); + return displayed; + } - var elemScript = GetElementScript(findBy, identifier, index); - var frame = GetFrame(data); - var script = $"window.getComputedStyle({elemScript}).display !== 'none';"; - var displayed = await frame.EvaluateExpressionAsync(script); + [Block("Checks if an element exists on the page", name = "Exists")] + public static async Task PuppeteerExists(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - data.Logger.Log($"Found out the element is{(displayed ? "" : " not")} displayed by executing {script}", LogColors.DarkSalmon); - return displayed; - } + var elemScript = GetElementScript(findBy, identifier, index); + var frame = GetFrame(data); + var script = $"window.getComputedStyle({elemScript}).display !== 'none';"; - [Block("Checks if an element exists on the page", name = "Exists")] - public static async Task PuppeteerExists(BotData data, FindElementBy findBy, string identifier, int index) + try { - data.Logger.LogHeader(); - - var elemScript = GetElementScript(findBy, identifier, index); - var frame = GetFrame(data); - var script = $"window.getComputedStyle({elemScript}).display !== 'none';"; - - try - { - var displayed = await frame.EvaluateExpressionAsync(script); - data.Logger.Log("The element exists", LogColors.DarkSalmon); - return true; - } - catch - { - data.Logger.Log("The element does not exist", LogColors.DarkSalmon); - return false; - } + await frame.EvaluateExpressionAsync(script); + data.Logger.Log("The element exists", LogColors.DarkSalmon); + return true; } - - [Block("Uploads one or more files to the selected element", name = "Upload Files")] - public static async Task PuppeteerUploadFiles(BotData data, FindElementBy findBy, string identifier, int index, List filePaths) + catch { - data.Logger.LogHeader(); + data.Logger.Log("The element does not exist", LogColors.DarkSalmon); + return false; + } + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - await elem.UploadFileAsync(filePaths.ToArray()); + [Block("Uploads one or more files to the selected element", name = "Upload Files")] + public static async Task PuppeteerUploadFiles(BotData data, FindElementBy findBy, string identifier, int index, List filePaths) + { + data.Logger.LogHeader(); - data.Logger.Log($"Uploaded {filePaths.Count} files to the element", LogColors.DarkSalmon); - } + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + await elem.UploadFileAsync(filePaths.ToArray()); - [Block("Gets the X coordinate of the element in pixels", name = "Get Position X")] - public static async Task PuppeteerGetPositionX(BotData data, FindElementBy findBy, string identifier, int index) - { - data.Logger.LogHeader(); + data.Logger.Log($"Uploaded {filePaths.Count} files to the element", LogColors.DarkSalmon); + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - var x = (int)(await elem.BoundingBoxAsync()).X; + [Block("Gets the X coordinate of the element in pixels", name = "Get Position X")] + public static async Task PuppeteerGetPositionX(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - data.Logger.Log($"The X coordinate of the element is {x}", LogColors.DarkSalmon); - return x; - } + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + var x = (int)(await elem.BoundingBoxAsync()).X; - [Block("Gets the Y coordinate of the element in pixels", name = "Get Position Y")] - public static async Task PuppeteerGetPositionY(BotData data, FindElementBy findBy, string identifier, int index) - { - data.Logger.LogHeader(); + data.Logger.Log($"The X coordinate of the element is {x}", LogColors.DarkSalmon); + return x; + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - var y = (int)(await elem.BoundingBoxAsync()).Y; + [Block("Gets the Y coordinate of the element in pixels", name = "Get Position Y")] + public static async Task PuppeteerGetPositionY(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - data.Logger.Log($"The Y coordinate of the element is {y}", LogColors.DarkSalmon); - return y; - } + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + var y = (int)(await elem.BoundingBoxAsync()).Y; - [Block("Gets the width of the element in pixels", name = "Get Width")] - public static async Task PuppeteerGetWidth(BotData data, FindElementBy findBy, string identifier, int index) - { - data.Logger.LogHeader(); + data.Logger.Log($"The Y coordinate of the element is {y}", LogColors.DarkSalmon); + return y; + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - var width = (int)(await elem.BoundingBoxAsync()).Width; + [Block("Gets the width of the element in pixels", name = "Get Width")] + public static async Task PuppeteerGetWidth(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - data.Logger.Log($"The width of the element is {width}", LogColors.DarkSalmon); - return width; - } + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + var width = (int)(await elem.BoundingBoxAsync()).Width; - [Block("Gets the height of the element in pixels", name = "Get Height")] - public static async Task PuppeteerGetHeight(BotData data, FindElementBy findBy, string identifier, int index) - { - data.Logger.LogHeader(); + data.Logger.Log($"The width of the element is {width}", LogColors.DarkSalmon); + return width; + } - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - var height = (int)(await elem.BoundingBoxAsync()).Height; + [Block("Gets the height of the element in pixels", name = "Get Height")] + public static async Task PuppeteerGetHeight(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - data.Logger.Log($"The height of the element is {height}", LogColors.DarkSalmon); - return height; - } + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + var height = (int)(await elem.BoundingBoxAsync()).Height; - [Block("Takes a screenshot of the element and saves it to an output file", name = "Screenshot Element")] - public static async Task PuppeteerScreenshotElement(BotData data, FindElementBy findBy, string identifier, int index, - string fileName, bool fullPage = false, bool omitBackground = false) - { - data.Logger.LogHeader(); - - if (data.Providers.Security.RestrictBlocksToCWD) - FileUtils.ThrowIfNotInCWD(fileName); - - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - await elem.ScreenshotAsync(fileName, new ScreenshotOptions - { - FullPage = fullPage, - OmitBackground = omitBackground, - Type = omitBackground ? ScreenshotType.Png : ScreenshotType.Jpeg, - Quality = omitBackground ? null : 100 - }); - - data.Logger.Log($"Took a screenshot of the element and saved it to {fileName}", LogColors.DarkSalmon); - } + data.Logger.Log($"The height of the element is {height}", LogColors.DarkSalmon); + return height; + } - [Block("Takes a screenshot of the element and converts it to a base64 string", name = "Screenshot Element Base64")] - public static async Task PuppeteerScreenshotBase64(BotData data, FindElementBy findBy, string identifier, int index, - bool fullPage = false, bool omitBackground = false) - { - data.Logger.LogHeader(); - - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - var base64 = await elem.ScreenshotBase64Async(new ScreenshotOptions - { - FullPage = fullPage, - OmitBackground = omitBackground, - Type = omitBackground ? ScreenshotType.Png : ScreenshotType.Jpeg, - Quality = omitBackground ? null : 100 - }); - - data.Logger.Log($"Took a screenshot of the element as base64", LogColors.DarkSalmon); - return base64; - } + [Block("Takes a screenshot of the element and saves it to an output file", name = "Screenshot Element")] + public static async Task PuppeteerScreenshotElement(BotData data, FindElementBy findBy, string identifier, int index, + string fileName, bool fullPage = false, bool omitBackground = false) + { + data.Logger.LogHeader(); + + if (data.Providers.Security.RestrictBlocksToCWD) + FileUtils.ThrowIfNotInCWD(fileName); - [Block("Switches to a different iframe", name = "Switch to Frame")] - public static async Task PuppeteerSwitchToFrame(BotData data, FindElementBy findBy, string identifier, int index) + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + await elem.ScreenshotAsync(fileName, new ScreenshotOptions { - data.Logger.LogHeader(); + FullPage = fullPage, + OmitBackground = omitBackground, + Type = omitBackground ? ScreenshotType.Png : ScreenshotType.Jpeg, + Quality = omitBackground ? null : 100 + }); - var frame = GetFrame(data); - var elem = await GetElement(frame, findBy, identifier, index); - data.SetObject("puppeteerFrame", await elem.ContentFrameAsync()); + data.Logger.Log($"Took a screenshot of the element and saved it to {fileName}", LogColors.DarkSalmon); + } - data.Logger.Log($"Switched to iframe", LogColors.DarkSalmon); - } + [Block("Takes a screenshot of the element and converts it to a base64 string", name = "Screenshot Element Base64")] + public static async Task PuppeteerScreenshotBase64(BotData data, FindElementBy findBy, string identifier, int index, + bool fullPage = false, bool omitBackground = false) + { + data.Logger.LogHeader(); + + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + var base64 = await elem.ScreenshotBase64Async(new ScreenshotOptions + { + FullPage = fullPage, + OmitBackground = omitBackground, + Type = omitBackground ? ScreenshotType.Png : ScreenshotType.Jpeg, + Quality = omitBackground ? null : 100 + }); + + data.Logger.Log("Took a screenshot of the element as base64", LogColors.DarkSalmon); + return base64; + } - [Block("Waits for an element to appear on the page", name = "Wait for Element")] - public static async Task PuppeteerWaitForElement(BotData data, FindElementBy findBy, string identifier, bool hidden = false, bool visible = true, - int timeout = 30000) - { - data.Logger.LogHeader(); + [Block("Switches to a different iframe", name = "Switch to Frame")] + public static async Task PuppeteerSwitchToFrame(BotData data, FindElementBy findBy, string identifier, int index) + { + data.Logger.LogHeader(); - var frame = GetFrame(data); - var options = new WaitForSelectorOptions { Hidden = hidden, Visible = visible, Timeout = timeout }; + var frame = GetFrame(data); + var elem = await GetElement(frame, findBy, identifier, index); + data.SetObject("puppeteerFrame", await elem.ContentFrameAsync()); - if (findBy == FindElementBy.XPath) - { - await frame.WaitForXPathAsync(identifier, options); - } - else - { - await frame.WaitForSelectorAsync(BuildSelector(findBy, identifier), options); - } + data.Logger.Log("Switched to iframe", LogColors.DarkSalmon); + } - data.Logger.Log($"Waited for element with {findBy} {identifier}", LogColors.DarkSalmon); - } + [Block("Waits for an element to appear on the page", name = "Wait for Element")] + public static async Task PuppeteerWaitForElement(BotData data, FindElementBy findBy, string identifier, bool hidden = false, bool visible = true, + int timeout = 30000) + { + data.Logger.LogHeader(); + + var frame = GetFrame(data); + var options = new WaitForSelectorOptions { Hidden = hidden, Visible = visible, Timeout = timeout }; - private static async Task GetElement(IFrame frame, FindElementBy findBy, string identifier, int index) + if (findBy == FindElementBy.XPath) { - var elements = await frame.QuerySelectorAllAsync(BuildSelector(findBy, identifier)); + await frame.WaitForXPathAsync(identifier, options); + } + else + { + await frame.WaitForSelectorAsync(BuildSelector(findBy, identifier), options); + } - if (elements.Length < index + 1) - { - throw new Exception($"Expected at least {index + 1} elements to be found but {elements.Length} were found"); - } + data.Logger.Log($"Waited for element with {findBy} {identifier}", LogColors.DarkSalmon); + } + + private static async Task GetElement(IFrame frame, FindElementBy findBy, string identifier, int index) + { + var elements = await frame.QuerySelectorAllAsync(BuildSelector(findBy, identifier)); - return elements[index]; + if (elements.Length < index + 1) + { + throw new Exception($"Expected at least {index + 1} elements to be found but {elements.Length} were found"); } - private static string GetElementsScript(FindElementBy findBy, string identifier) + return elements[index]; + } + + private static string GetElementsScript(FindElementBy findBy, string identifier) + { + if (findBy == FindElementBy.XPath) + { + var script = $"document.evaluate(\"{identifier.Replace("\"", "\\\"")}\", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)"; + return $"Array.from({{ length: {script}.snapshotLength }}, (_, index) => {script}.snapshotItem(index))"; + } + else { - if (findBy == FindElementBy.XPath) - { - var script = $"document.evaluate(\"{identifier.Replace("\"", "\\\"")}\", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)"; - return $"Array.from({{ length: {script}.snapshotLength }}, (_, index) => {script}.snapshotItem(index))"; - } - else - { - return $"document.querySelectorAll('{BuildSelector(findBy, identifier)}')"; - } + return $"document.querySelectorAll('{BuildSelector(findBy, identifier)}')"; } + } - private static string GetElementScript(FindElementBy findBy, string identifier, int index) - => findBy == FindElementBy.XPath + private static string GetElementScript(FindElementBy findBy, string identifier, int index) + => findBy == FindElementBy.XPath ? $"document.evaluate(\"{identifier.Replace("\"", "\\\"")}\", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue" : $"document.querySelectorAll('{BuildSelector(findBy, identifier)}')[{index}]"; - private static string BuildSelector(FindElementBy findBy, string identifier) - => findBy switch - { - FindElementBy.Id => '#' + identifier, - FindElementBy.Class => '.' + string.Join('.', identifier.Split(' ')), // "class1 class2" => ".class1.class2" - FindElementBy.Selector => identifier, - _ => throw new NotSupportedException() - }; + private static string BuildSelector(FindElementBy findBy, string identifier) + => findBy switch + { + FindElementBy.Id => '#' + identifier, + FindElementBy.Class => '.' + string.Join('.', identifier.Split(' ')), // "class1 class2" => ".class1.class2" + FindElementBy.Selector => identifier, + _ => throw new NotSupportedException() + }; - private static IBrowser GetBrowser(BotData data) - => data.TryGetObject("puppeteer") ?? throw new Exception("The browser is not open!"); + private static IBrowser GetBrowser(BotData data) + => data.TryGetObject("puppeteer") ?? throw new Exception("The browser is not open!"); - private static IPage GetPage(BotData data) - => data.TryGetObject("puppeteerPage") ?? throw new Exception("No pages open!"); + private static IPage GetPage(BotData data) + => data.TryGetObject("puppeteerPage") ?? throw new Exception("No pages open!"); - private static IFrame GetFrame(BotData data) - => data.TryGetObject