diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..14142b7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 + +updates: +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + time: "11:00" + +- package-ecosystem: nuget + directory: "/" + schedule: + interval: daily + time: "11:00" \ No newline at end of file diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 2bce8a0..f52e8e0 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -7,12 +7,18 @@ on: env: GLOBAL_JSON_PATH: './src/global.json' - DOTNET_VERSION: '10.0.x' PROJECT_PATH: './src/signal.sln' PACKAGE_OUTPUT_DIRECTORY: './packages' + TEST_OUTPUT_DIRECTORY: './testresults' + +permissions: + contents: write + deployments: write jobs: build: + permissions: + contents: read # For a list of available runner types, refer to # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on runs-on: ubuntu-latest @@ -48,13 +54,32 @@ jobs: run: dotnet restore ${{ env.PROJECT_PATH }} - name: Build - run: dotnet build --configuration Release --no-restore ${{ env.PROJECT_PATH }} /p:Version=${{ steps.gitversion.outputs.semVer }} + run: > + dotnet build + --configuration Release + --no-restore + ${{ env.PROJECT_PATH }} + /p:Version=${{ steps.gitversion.outputs.semVer }} - name: Run tests - run: dotnet test --configuration Release --no-build --verbosity normal ${{ env.PROJECT_PATH }} + run: > + dotnet test + --configuration Release + --no-build + --collect:"XPlat Code Coverage;Format=opencover" + --logger trx + --results-directory ${{ env.TEST_OUTPUT_DIRECTORY }} + --verbosity normal + ${{ env.PROJECT_PATH }} - name: Create NuGet package - run: dotnet pack --configuration Release --no-build --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} ${{ env.PROJECT_PATH }} /p:PackageVersion=${{ steps.gitversion.outputs.semVer }} + run: > + dotnet pack + --configuration Release + --no-build + --output ${{ env.PACKAGE_OUTPUT_DIRECTORY }} + ${{ env.PROJECT_PATH }} + /p:PackageVersion=${{ steps.gitversion.outputs.semVer }} - name: Upload package artifacts uses: actions/upload-artifact@v4 @@ -63,8 +88,14 @@ jobs: path: ${{ env.PACKAGE_OUTPUT_DIRECTORY }}/*.nupkg publish: + permissions: + contents: write + deployments: write runs-on: ubuntu-latest needs: build + environment: + name: production + url: https://www.nuget.org/packages/Signal.Bot if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: @@ -82,7 +113,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ env.DOTNET_VERSION }} + global-json-file: ${{ env.GLOBAL_JSON_PATH }} - name: Publish to NuGet run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..7dc5305 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,74 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main", feature/*, ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '38 2 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" \ No newline at end of file diff --git a/README.md b/README.md index 891832d..a8f141e 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ > A .NET Signal Messenger Bot Client - because sometimes Telegram isn't enough [![NuGet](https://img.shields.io/nuget/v/Signal.Bot.svg)](https://www.nuget.org/packages/Signal.Bot/) -[![Build Status](https://img.shields.io/github/actions/workflow/status/st0o0/signal.bot/dotnet.yml?branch=main)](https://github.com/st0o0/signal.bot/actions) -[![License](https://img.shields.io/github/license/st0o0/signal.bot)](LICENSE) +[![Build Status](https://img.shields.io/github/actions/workflow/status/st0o0/signal.bot/build-and-release.yml?branch=main)](https://github.com/st0o0/Signal.Bot/actions) +[![License](https://img.shields.io/github/license/st0o0/Signal.Bot)](LICENSE) [![Downloads](https://img.shields.io/nuget/dt/Signal.Bot.svg)](https://www.nuget.org/packages/Signal.Bot/) diff --git a/src/Signal.Bot.Example.Env/Apphost.cs b/src/Signal.Bot.Example.Env/Apphost.cs index f4a757e..a35cba5 100644 --- a/src/Signal.Bot.Example.Env/Apphost.cs +++ b/src/Signal.Bot.Example.Env/Apphost.cs @@ -1,4 +1,5 @@ var builder = DistributedApplication.CreateBuilder(args); +var number = builder.AddParameter("NUMBER", secret: true); var signalCli = builder .AddContainer("signal-cli-rest-api", "bbernhard/signal-cli-rest-api") @@ -10,6 +11,7 @@ var signalClient = builder .AddProject("signal-bot-example") + .WithEnvironment("NUMBER", number) .WaitFor(signalCli); await builder.Build().RunAsync(); \ No newline at end of file diff --git a/src/Signal.Bot.Example/Program.cs b/src/Signal.Bot.Example/Program.cs index 98e94d1..ed720e8 100644 --- a/src/Signal.Bot.Example/Program.cs +++ b/src/Signal.Bot.Example/Program.cs @@ -2,7 +2,7 @@ using Signal.Bot.Example; var builder = WebApplication.CreateBuilder(args); -Console.WriteLine($"Phone {Environment.GetEnvironmentVariable("NUMBER")!}"); +Console.WriteLine($"Phone {Environment.GetEnvironmentVariable("NUMBER")! }"); builder.Services .AddHttpClient("signalbot_client", client => client.BaseAddress = new Uri("http://localhost:1337")) .AddTypedClient((httpClient, sp) => diff --git a/src/Signal.Bot.Example/Sample.cs b/src/Signal.Bot.Example/Sample.cs index bf7ef20..9d458b9 100644 --- a/src/Signal.Bot.Example/Sample.cs +++ b/src/Signal.Bot.Example/Sample.cs @@ -23,13 +23,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) //var t1 = await client.GetAboutAsync(stoppingToken); //var t2 = await client.GetAccountsAsync(stoppingToken); //var t4 = await client.GetContactsAsync(stoppingToken); - var t3 = await client.GetDevicesAsync(stoppingToken); - foreach (var device in t3) - { - var created = DateTimeOffset.FromUnixTimeMilliseconds(device.Created ?? 0); - logger.LogInformation("Created: {DateTimeOffset}", created.UtcDateTime); - var lastSeen = DateTimeOffset.FromUnixTimeMilliseconds(device.LastSeen ?? 0); - logger.LogInformation("LastSeen: {DateTimeOffset}", lastSeen.UtcDateTime); - } + //var t3 = await client.GetDevicesAsync(stoppingToken); + //var t5 = await client.GetGroupsAsync(stoppingToken); + var t6 = await client.GetIdentitiesAsync(stoppingToken); + var t7 = (await client.GetAttachmentsAsync(stoppingToken)).ToArray(); + var t8 = await client.GetAttachmentAsync(t7[0], stoppingToken); } } \ No newline at end of file diff --git a/src/Signal.Bot.Example/Signal.Bot.Example.csproj b/src/Signal.Bot.Example/Signal.Bot.Example.csproj index e94082b..0776758 100644 --- a/src/Signal.Bot.Example/Signal.Bot.Example.csproj +++ b/src/Signal.Bot.Example/Signal.Bot.Example.csproj @@ -8,7 +8,6 @@ - diff --git a/src/Signal.Bot.Tests/Signal.Bot.Tests.csproj b/src/Signal.Bot.Tests/Signal.Bot.Tests.csproj index a8a7e07..be7651d 100644 --- a/src/Signal.Bot.Tests/Signal.Bot.Tests.csproj +++ b/src/Signal.Bot.Tests/Signal.Bot.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Signal.Bot.Tests/SignalBotClientExtensionsTests.cs b/src/Signal.Bot.Tests/SignalBotClientExtensionsTests.cs index 6f8dd0b..075aaa2 100644 --- a/src/Signal.Bot.Tests/SignalBotClientExtensionsTests.cs +++ b/src/Signal.Bot.Tests/SignalBotClientExtensionsTests.cs @@ -1,4 +1,4 @@ -using Moq; +using NSubstitute; using Signal.Bot.Requests; using Signal.Bot.Types; @@ -6,37 +6,38 @@ namespace Signal.Bot.Tests; public class SignalBotClientExtensionsTests { - private readonly Mock _client = new(MockBehavior.Strict); + private readonly ISignalBotClient _client; public SignalBotClientExtensionsTests() { - _client.Setup(x => x.Number).Returns("123"); + _client = Substitute.For(); + _client.Number.Returns("123"); } - + [Fact] public async Task SendMessageAsync_SendsCorrectRequest() { // Arrange var expectedResponse = new SendMessage(); _client - .Setup(c => c.SendRequestAsync( - It.Is(r => + .SendRequestAsync(Arg.Is(r => r.Message == "hello" && r.Number == "123" && - r.Recipients.Count == 1 && + r.Recipients!.Count == 1 && r.Recipients.Contains("456")), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(expectedResponse); + Arg.Any(), + Arg.Any()) + .Returns(expectedResponse); // Act - var result = await _client.Object.SendMessageAsync( + var result = await _client.SendMessageAsync( recipient: "456", message: "hello"); // Assert Assert.Same(expectedResponse, result); - _client.VerifyAll(); + await _client.Received(1).SendRequestAsync(Arg.Any(), Arg.Any(), + Arg.Any()); } [Fact] @@ -45,18 +46,18 @@ public async Task GetAboutAsync_SendsGetAboutRequest() // Arrange var about = new About(); _client - .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny(), - It.IsAny())) - .ReturnsAsync(about); + .SendRequestAsync(Arg.Any(), + Arg.Any(), + Arg.Any()) + .Returns(about); // Act - var result = await _client.Object.GetAboutAsync(); + var result = await _client.GetAboutAsync(); // Assert Assert.Same(about, result); - _client.Verify(c => - c.SendRequestAsync(It.IsAny(), It.IsAny(), It.IsAny()), - Times.Once); + await _client.Received(1) + .SendRequestAsync(Arg.Any(), Arg.Any(), Arg.Any()); } [Fact] @@ -64,21 +65,22 @@ public async Task RegisterNumberAsync_SetsOptionalValuesCorrectly() { // Arrange _client - .Setup(c => c.SendRequestAsync( - It.Is(r => + .SendRequestAsync(Arg.Is(r => r.Number == "123" && r.Captcha == "captcha-token" && - r.UseVoice == true), It.IsAny(), - It.IsAny())) + r.UseVoice == true), + Arg.Any(), + Arg.Any()) .Returns(Task.CompletedTask); // Act - await _client.Object.RegisterNumberAsync( + await _client.RegisterNumberAsync( captcha: "captcha-token", useVoice: true); // Assert - _client.VerifyAll(); + await _client.Received(1).SendRequestAsync(Arg.Any(), Arg.Any(), + Arg.Any()); } [Fact] @@ -86,20 +88,21 @@ public async Task SetTypingIndicatorAsync_WhenIsTypingFalse_UsesRemoveRequest() { // Arrange _client - .Setup(c => c.SendRequestAsync( - It.Is(r => + .SendRequestAsync(Arg.Is(r => r.Number == "123" && - r.Recipient == "456"), It.IsAny(), - It.IsAny())) + r.Recipient == "456"), + Arg.Any(), + Arg.Any()) .Returns(Task.CompletedTask); // Act - await _client.Object.SetTypingIndicatorAsync( + await _client.SetTypingIndicatorAsync( recipient: "456", isTyping: false); // Assert - _client.VerifyAll(); + await _client.Received(1).SendRequestAsync(Arg.Any(), Arg.Any(), + Arg.Any()); } [Fact] @@ -107,22 +110,23 @@ public async Task AddGroupMemberAsync_SendsCorrectMembers() { // Arrange var members = new List { "a", "b" }; - + _client - .Setup(c => c.SendRequestAsync( - It.Is(r => + .SendRequestAsync(Arg.Is(r => r.Number == "123" && r.GroupId == "group1" && - r.Members == members), It.IsAny(), - It.IsAny())) + r.Members == members), + Arg.Any(), + Arg.Any()) .Returns(Task.CompletedTask); // Act - await _client.Object.AddGroupMemberAsync( + await _client.AddGroupMemberAsync( groupId: "group1", members: members); // Assert - _client.VerifyAll(); + await _client.Received(1).SendRequestAsync(Arg.Any(), Arg.Any(), + Arg.Any()); } } \ No newline at end of file diff --git a/src/Signal.Bot.Tests/SignalBotClientTests.cs b/src/Signal.Bot.Tests/SignalBotClientTests.cs new file mode 100644 index 0000000..fa9c2dd --- /dev/null +++ b/src/Signal.Bot.Tests/SignalBotClientTests.cs @@ -0,0 +1,54 @@ +using NSubstitute; + +namespace Signal.Bot.Tests; + +public class SignalBotClientTests +{ + private readonly HttpClient _httpClientMock; + private readonly SignalBotClient _client; + + public SignalBotClientTests() + { + _httpClientMock = Substitute.For(); + _client = new SignalBotClient(new SignalBotClientOptions("123", "http://localhost:8080"), _httpClientMock); + } + + [Fact] + public async Task SendMessageAsync_ValidRequest_CallsHttpClient() + { + // Arrange + _httpClientMock + .SendAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK))); + + // Act + await _client.SendMessageAsync( + ["+0987654321"], + "Hello World", + CancellationToken.None); + + // Assert + await _httpClientMock + .Received(1) + .SendAsync( + Arg.Is(req => + req.Method == HttpMethod.Post && req.RequestUri!.ToString().Contains("v2/send")), + Arg.Any()); + } + + [Fact] + public async Task SendMessageAsync_NullMessage_ThrowsArgumentNullException() + { + await Assert.ThrowsAsync(async () => + await _client.SendMessageAsync(["+0987654321"], null!)); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public async Task SendMessageAsync_EmptyMessage_ThrowsArgumentException(string message) + { + await Assert.ThrowsAsync(async () => + await _client.SendMessageAsync(["+0987654321"], message)); + } +} \ No newline at end of file diff --git a/src/Signal.Bot/ISignalBotClient.cs b/src/Signal.Bot/ISignalBotClient.cs index d9c9df4..2eee243 100644 --- a/src/Signal.Bot/ISignalBotClient.cs +++ b/src/Signal.Bot/ISignalBotClient.cs @@ -20,14 +20,14 @@ public interface ISignalBotClient IObservable OnApiResponse { get; } IObservable OnException { get; } + Task SendRequestAsync( + IRequest request, + string[]? queryParameters = null, + CancellationToken cancellationToken = default); + Task SendRequestAsync( IRequest request, string[]? queryParameters = null, CancellationToken cancellationToken = default ); - - Task SendRequestAsync( - IRequest request, - string[]? queryParameters = null, - CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Signal.Bot/Serialization/JsonBotAPI.cs b/src/Signal.Bot/Serialization/JsonBotAPI.cs index 63810ac..167fff6 100644 --- a/src/Signal.Bot/Serialization/JsonBotAPI.cs +++ b/src/Signal.Bot/Serialization/JsonBotAPI.cs @@ -1,23 +1,16 @@ -using System; -using System.Text.Json; +using System.Text.Json; namespace Signal.Bot.Serialization; -public class JsonBotAPI +public static class JsonBotAPI { public static JsonSerializerOptions Options { get; } static JsonBotAPI() => Configure(Options = new JsonSerializerOptions()); - public static JsonSerializerOptions Configure(Action? configure = null) - => Configure(Options, configure); - - public static JsonSerializerOptions Configure(JsonSerializerOptions options, - Action? configure = null) + public static void Configure(JsonSerializerOptions options) { - configure?.Invoke(options); options.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault; - return options; } } \ No newline at end of file diff --git a/src/Signal.Bot/SignalBotClient.cs b/src/Signal.Bot/SignalBotClient.cs index 1f0e752..2af4677 100644 --- a/src/Signal.Bot/SignalBotClient.cs +++ b/src/Signal.Bot/SignalBotClient.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Net.Http.Json; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text.Json; @@ -9,7 +8,6 @@ using Signal.Bot.Args; using Signal.Bot.Exceptions; using Signal.Bot.Requests; -using Signal.Bot.Serialization; namespace Signal.Bot; @@ -44,14 +42,12 @@ public SignalBotClient(SignalBotClientOptions options, HttpClient httpClient, public JsonSerializerOptions JsonSerializerOptions { get; } public CancellationToken GlobalCancelToken { get; } - public TimeSpan Timeout => _httpClient.Timeout; public IObservable OnApiRequest => _onApiRequest.AsObservable(); public IObservable OnApiResponse => _onApiResponse.AsObservable(); public IObservable OnException => _onException.AsObservable(); - public async Task SendRequestAsync(IRequest request, - string[]? queryParameters = null, + public async Task SendAsync(IRequest request, string[]? queryParameters = null, CancellationToken cancellationToken = default) { try @@ -95,9 +91,7 @@ public async Task SendRequestAsync(IRequest req _onApiResponse.OnNext(new OnApiResponseArgs(request, httpRequest, httpResponse)); try { - return (await httpResponse.Content - .ReadFromJsonAsync(JsonBotAPI.Options, cancellationToken) - .ConfigureAwait(false))!; + return httpResponse; } catch (Exception exception) { @@ -114,38 +108,35 @@ public async Task SendRequestAsync(IRequest req } } - public async Task SendRequestAsync(IRequest request, string[]? queryParameters = null, + public async Task SendRequestAsync( + IRequest request, + string[]? queryParameters = null, + CancellationToken cancellationToken = default) + { + _ = await SendAsync(request, queryParameters, cancellationToken).ConfigureAwait(false); + } + + public async Task SendRequestAsync(IRequest request, + string[]? queryParameters = null, CancellationToken cancellationToken = default) { try { - var methodName = request.MethodName; - var query = new List(queryParameters ?? []); - if (request is SearchNumbersRequest { Numbers: not null } searchRequest) - { - query.AddRange(searchRequest.Numbers.Select(num => $"numbers={Uri.EscapeDataString(num)}")); - } - - if (query.Count > 0) - { - methodName += "?" + string.Join("&", query); - } - - var httpRequest = new HttpRequestMessage(request.HttpMethod, methodName) - { - Content = request.ToHttpContent() - }; - - _onApiRequest.OnNext(new OnApiRequestArgs(request, httpRequest)); - using var httpResponse = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); - _onApiResponse.OnNext(new OnApiResponseArgs(request, httpRequest, httpResponse)); - httpResponse.EnsureSuccessStatusCode(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(GlobalCancelToken, cancellationToken); + cancellationToken = cts.Token; + var httpResponse = await SendAsync(request, queryParameters, cancellationToken); + var content = await httpResponse.Content.ReadAsStringAsync(cancellationToken); + return JsonSerializer.Deserialize(content, JsonSerializerOptions)!; + // return await httpResponse.Content + // .ReadFromJsonAsync(JsonBotAPI.Options, cancellationToken) + // .ConfigureAwait(false)!; } catch (Exception ex) { _onException.OnNext(ex); _onApiRequest.OnError(ex); _onApiResponse.OnError(ex); + return default!; } } } \ No newline at end of file diff --git a/src/Signal.Bot/SignalBotClientExtensions.cs b/src/Signal.Bot/SignalBotClientExtensions.cs index 3672026..68aaa5c 100644 --- a/src/Signal.Bot/SignalBotClientExtensions.cs +++ b/src/Signal.Bot/SignalBotClientExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; using Signal.Bot.Requests; @@ -5,42 +6,50 @@ namespace Signal.Bot; +// ReSharper disable once ConvertToExtensionBlock public static partial class SignalBotClientExtensions { - public static async Task SendMessageAsync( - this ISignalBotClient client, + public static async Task SendMessageAsync(this ISignalBotClient client, string recipient, string message, CancellationToken cancellationToken = default) { + return await client.SendMessageAsync([recipient], message, cancellationToken); + } + + public static async Task SendMessageAsync(this ISignalBotClient client, + string[] recipients, + string message, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(message); return await client.SendRequestAsync(new SendMessageRequest { - Recipients = new List { recipient }, + Recipients = recipients, Message = message, Number = client.Number }, cancellationToken: cancellationToken); } - public static async Task GetAboutAsync( - this ISignalBotClient client, CancellationToken cancellationToken = default) + public static async Task GetAboutAsync(this ISignalBotClient client, + CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetAboutRequest(), cancellationToken: cancellationToken); } - public static async Task> GetAccountsAsync( - this ISignalBotClient client, CancellationToken cancellationToken = default) + public static async Task> GetAccountsAsync(this ISignalBotClient client, + CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetAccountsRequest(), cancellationToken: cancellationToken); } - public static async Task> GetGroupsAsync( - this ISignalBotClient client, CancellationToken cancellationToken = default) + public static async Task> GetGroupsAsync(this ISignalBotClient client, + CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetGroupsRequest(client.Number), cancellationToken: cancellationToken); } - public static async Task RegisterNumberAsync( - this ISignalBotClient client, + public static async Task RegisterNumberAsync(this ISignalBotClient client, string? captcha = null, bool? useVoice = null, CancellationToken cancellationToken = default) @@ -52,8 +61,7 @@ await client.SendRequestAsync(new RegisterNumberRequest(client.Number) }, cancellationToken: cancellationToken); } - public static async Task VerifyNumberAsync( - this ISignalBotClient client, + public static async Task VerifyNumberAsync(this ISignalBotClient client, string token, string? pin = null, CancellationToken cancellationToken = default) @@ -64,8 +72,7 @@ public static async Task VerifyNumberAsync( }, cancellationToken: cancellationToken); } - public static async Task UpdateProfileAsync( - this ISignalBotClient client, + public static async Task UpdateProfileAsync(this ISignalBotClient client, string? name = null, string? about = null, string? base64Avatar = null, @@ -79,8 +86,7 @@ await client.SendRequestAsync(new UpdateProfileRequest(client.Number) }, cancellationToken: cancellationToken); } - public static async Task SetTypingIndicatorAsync( - this ISignalBotClient client, + public static async Task SetTypingIndicatorAsync(this ISignalBotClient client, string? recipient = null, string? groupId = null, bool isTyping = true, @@ -100,24 +106,20 @@ public static async Task SetTypingIndicatorAsync( } // Account extensions - public static async Task SetPinAsync( - this ISignalBotClient client, - string pin, + public static async Task SetPinAsync(this ISignalBotClient client, string pin, CancellationToken cancellationToken = default) { await client.SendRequestAsync(new SetPinRequest(client.Number) { Pin = pin }, cancellationToken: cancellationToken); } - public static async Task RemovePinAsync( - this ISignalBotClient client, + public static async Task RemovePinAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { await client.SendRequestAsync(new RemovePinRequest(client.Number), cancellationToken: cancellationToken); } - public static async Task RateLimitChallengeAsync( - this ISignalBotClient client, + public static async Task RateLimitChallengeAsync(this ISignalBotClient client, string challengeToken, string captcha, CancellationToken cancellationToken = default) @@ -129,8 +131,7 @@ await client.SendRequestAsync(new RateLimitChallengeRequest(client.Number) }, cancellationToken: cancellationToken); } - public static async Task UpdateAccountSettingsAsync( - this ISignalBotClient client, + public static async Task UpdateAccountSettingsAsync(this ISignalBotClient client, bool discoverableByNumber = true, bool shareNumberWithContacts = true, CancellationToken cancellationToken = default) @@ -142,22 +143,21 @@ await client.SendRequestAsync(new UpdateAccountSettingsRequest(client.Number) }, cancellationToken: cancellationToken); } - public static async Task SetUsernameAsync( - this ISignalBotClient client, string username, CancellationToken cancellationToken = default) + public static async Task SetUsernameAsync(this ISignalBotClient client, string username, + CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new SetUsernameRequest(client.Number) { Username = username }, cancellationToken: cancellationToken); } - public static async Task RemoveUsernameAsync( - this ISignalBotClient client, CancellationToken cancellationToken = default) + public static async Task RemoveUsernameAsync(this ISignalBotClient client, + CancellationToken cancellationToken = default) { await client.SendRequestAsync(new RemoveUsernameRequest(client.Number), cancellationToken: cancellationToken); } // Device extensions - public static async Task> GetDevicesAsync( - this ISignalBotClient client, + public static async Task> GetDevicesAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetDevicesRequest(client.Number), @@ -171,23 +171,20 @@ await client.SendRequestAsync(new AddDeviceRequest(client.Number) { Uri = uri }, cancellationToken: cancellationToken); } - public static async Task UnregisterDeviceAsync( - this ISignalBotClient client, + public static async Task UnregisterDeviceAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { await client.SendRequestAsync(new UnregisterDeviceRequest(client.Number), cancellationToken: cancellationToken); } // Content extensions - public static async Task> GetAttachmentsAsync( - this ISignalBotClient client, + public static async Task> GetAttachmentsAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetAttachmentsRequest(), cancellationToken: cancellationToken); } - public static async Task GetAttachmentAsync( - this ISignalBotClient client, + public static async Task GetAttachmentAsync(this ISignalBotClient client, string attachmentId, CancellationToken cancellationToken = default) { @@ -195,16 +192,14 @@ public static async Task GetAttachmentAsync( cancellationToken: cancellationToken); } - public static async Task RemoveAttachmentAsync( - this ISignalBotClient client, + public static async Task RemoveAttachmentAsync(this ISignalBotClient client, string attachmentId, CancellationToken cancellationToken = default) { await client.SendRequestAsync(new RemoveAttachmentRequest(attachmentId), cancellationToken: cancellationToken); } - public static async Task AddReactionAsync( - this ISignalBotClient client, + public static async Task AddReactionAsync(this ISignalBotClient client, string reaction, string recipient, string targetAuthor, @@ -220,8 +215,7 @@ await client.SendRequestAsync(new AddReactionRequest(client.Number) }, cancellationToken: cancellationToken); } - public static async Task RemoveReactionAsync( - this ISignalBotClient client, + public static async Task RemoveReactionAsync(this ISignalBotClient client, string emoji, string recipient, string targetAuthor, @@ -237,8 +231,7 @@ public static async Task RemoveReactionAsync( }, cancellationToken: cancellationToken); } - public static async Task RemoteDeleteAsync( - this ISignalBotClient client, + public static async Task RemoteDeleteAsync(this ISignalBotClient client, string recipient, int timestamp, CancellationToken cancellationToken = default) @@ -250,16 +243,14 @@ public static async Task RemoteDeleteAsync( }, cancellationToken: cancellationToken); } - public static async Task> GetStickerPacksAsync( - this ISignalBotClient client, + public static async Task> GetStickerPacksAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetStickerPacksRequest(client.Number), cancellationToken: cancellationToken); } - public static async Task AddStickerPackAsync( - this ISignalBotClient client, + public static async Task AddStickerPackAsync(this ISignalBotClient client, string packId, string packKey, CancellationToken cancellationToken = default) @@ -272,16 +263,14 @@ await client.SendRequestAsync(new AddStickerPackRequest(client.Number) } // Social extensions - public static async Task> GetContactsAsync( - this ISignalBotClient client, + public static async Task> GetContactsAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetContactsRequest(client.Number), cancellationToken: cancellationToken); } - public static async Task UpdateContactAsync( - this ISignalBotClient client, + public static async Task UpdateContactAsync(this ISignalBotClient client, string recipient, string? name = null, int? expirationTime = null, @@ -295,15 +284,13 @@ await client.SendRequestAsync(new UpdateContactRequest(client.Number) }, cancellationToken: cancellationToken); } - public static async Task SyncContactsAsync( - this ISignalBotClient client, + public static async Task SyncContactsAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { await client.SendRequestAsync(new SyncContactsRequest(client.Number), cancellationToken: cancellationToken); } - public static async Task GetGroupAsync( - this ISignalBotClient client, + public static async Task GetGroupAsync(this ISignalBotClient client, string groupId, CancellationToken cancellationToken = default) { @@ -311,8 +298,7 @@ public static async Task GetGroupAsync( cancellationToken: cancellationToken); } - public static async Task RemoveGroupAsync( - this ISignalBotClient client, + public static async Task RemoveGroupAsync(this ISignalBotClient client, string groupId, CancellationToken cancellationToken = default) { @@ -320,8 +306,7 @@ await client.SendRequestAsync(new RemoveGroupRequest(client.Number, groupId), cancellationToken: cancellationToken); } - public static async Task AddGroupAdminAsync( - this ISignalBotClient client, + public static async Task AddGroupAdminAsync(this ISignalBotClient client, string groupId, ICollection admins, CancellationToken cancellationToken = default) @@ -330,8 +315,7 @@ await client.SendRequestAsync(new AddGroupAdminRequest(client.Number, groupId) { cancellationToken: cancellationToken); } - public static async Task RemoveGroupAdminAsync( - this ISignalBotClient client, + public static async Task RemoveGroupAdminAsync(this ISignalBotClient client, string groupId, ICollection admins, CancellationToken cancellationToken = default) @@ -340,8 +324,7 @@ await client.SendRequestAsync(new RemoveGroupAdminRequest(client.Number, groupId cancellationToken: cancellationToken); } - public static async Task AddGroupMemberAsync( - this ISignalBotClient client, + public static async Task AddGroupMemberAsync(this ISignalBotClient client, string groupId, ICollection members, CancellationToken cancellationToken = default) @@ -350,8 +333,7 @@ await client.SendRequestAsync(new AddGroupMemberRequest(client.Number, groupId) cancellationToken: cancellationToken); } - public static async Task RemoveGroupMemberAsync( - this ISignalBotClient client, + public static async Task RemoveGroupMemberAsync(this ISignalBotClient client, string groupId, ICollection members, CancellationToken cancellationToken = default) @@ -360,8 +342,7 @@ await client.SendRequestAsync(new RemoveGroupMemberRequest(client.Number, groupI cancellationToken: cancellationToken); } - public static async Task QuitGroupAsync( - this ISignalBotClient client, + public static async Task QuitGroupAsync(this ISignalBotClient client, string groupId, CancellationToken cancellationToken = default) { @@ -369,16 +350,14 @@ await client.SendRequestAsync(new QuitGroupRequest(client.Number, groupId), cancellationToken: cancellationToken); } - public static async Task> GetIdentitiesAsync( - this ISignalBotClient client, + public static async Task> GetIdentitiesAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetIdentitiesRequest(client.Number), cancellationToken: cancellationToken); } - public static async Task TrustIdentityAsync( - this ISignalBotClient client, + public static async Task TrustIdentityAsync(this ISignalBotClient client, string verifiedNumber, bool? trustAllKnownKeys = null, string? verifiedSafetyNumber = null, @@ -391,8 +370,7 @@ await client.SendRequestAsync(new TrustIdentityRequest(client.Number, verifiedNu }, cancellationToken: cancellationToken); } - public static async Task> SearchNumbersAsync( - this ISignalBotClient client, + public static async Task> SearchNumbersAsync(this ISignalBotClient client, IEnumerable numbers, CancellationToken cancellationToken = default) { @@ -403,15 +381,13 @@ public static async Task> SearchNumbersAsync( } // Configuration extensions - public static async Task GetConfigurationAsync( - this ISignalBotClient client, + public static async Task GetConfigurationAsync(this ISignalBotClient client, CancellationToken cancellationToken = default) { return await client.SendRequestAsync(new GetConfigurationRequest(), cancellationToken: cancellationToken); } - public static async Task SetConfigurationAsync( - this ISignalBotClient client, + public static async Task SetConfigurationAsync(this ISignalBotClient client, string logging, CancellationToken cancellationToken = default) { diff --git a/src/Signal.Bot/SignalBotClientOptions.cs b/src/Signal.Bot/SignalBotClientOptions.cs index 4beea3f..382dd46 100644 --- a/src/Signal.Bot/SignalBotClientOptions.cs +++ b/src/Signal.Bot/SignalBotClientOptions.cs @@ -4,9 +4,9 @@ namespace Signal.Bot; -public class SignalBotClientOptions(string number, string baseUrl, Action? configure = null) +public class SignalBotClientOptions(string number, string baseUrl) { - public JsonSerializerOptions JsonSerializerOptions => JsonBotAPI.Configure(configure); + public JsonSerializerOptions JsonSerializerOptions { get; } = JsonBotAPI.Options; public string BaseUrl { get; } = baseUrl ?? throw new ArgumentNullException(nameof(baseUrl)); public string Number { get; } = number ?? throw new ArgumentNullException(nameof(number)); } \ No newline at end of file diff --git a/src/Signal.Bot/Types/ReceivedMessage.cs b/src/Signal.Bot/Types/ReceivedMessage.cs index 5d93e84..3fc6d33 100644 --- a/src/Signal.Bot/Types/ReceivedMessage.cs +++ b/src/Signal.Bot/Types/ReceivedMessage.cs @@ -30,112 +30,112 @@ public class Envelope public class DataMessage { - [JsonPropertyName("timestamp")] public long? Timestamp { get; set; } = null; + [JsonPropertyName("timestamp")] public long? Timestamp { get; set; } - [JsonPropertyName("body")] public string? Body { get; set; } = null; + [JsonPropertyName("body")] public string? Body { get; set; } - [JsonPropertyName("attachments")] public List? Attachments { get; set; } = null; + [JsonPropertyName("attachments")] public List? Attachments { get; set; } - [JsonPropertyName("groupV2")] public GroupV2Info? GroupV2 { get; set; } = null; + [JsonPropertyName("groupV2")] public GroupV2Info? GroupV2 { get; set; } - [JsonPropertyName("reaction")] public ReactionData? Reaction { get; set; } = null; + [JsonPropertyName("reaction")] public ReactionData? Reaction { get; set; } - [JsonPropertyName("mentions")] public List Mentions { get; set; } + [JsonPropertyName("mentions")] public List? Mentions { get; set; } - [JsonPropertyName("quote")] public QuoteData Quote { get; set; } + [JsonPropertyName("quote")] public QuoteData? Quote { get; set; } - [JsonPropertyName("readMessages")] public List ReadMessages { get; set; } + [JsonPropertyName("readMessages")] public List? ReadMessages { get; set; } - [JsonPropertyName("viewOnce")] public bool ViewOnce { get; set; } + [JsonPropertyName("viewOnce")] public bool? ViewOnce { get; set; } - [JsonPropertyName("previews")] public List Previews { get; set; } + [JsonPropertyName("previews")] public List? Previews { get; set; } } public class SyncMessage { - [JsonPropertyName("sentMessage")] public DataMessage SentMessage { get; set; } + [JsonPropertyName("sentMessage")] public DataMessage? SentMessage { get; set; } } public class TypingMessage { - [JsonPropertyName("timestamp")] public long Timestamp { get; set; } + [JsonPropertyName("timestamp")] public long? Timestamp { get; set; } - [JsonPropertyName("action")] public string Action { get; set; } + [JsonPropertyName("action")] public string? Action { get; set; } } public class ReceiptMessage { - [JsonPropertyName("timestamps")] public List Timestamps { get; set; } + [JsonPropertyName("timestamps")] public List? Timestamps { get; set; } - [JsonPropertyName("type")] public string Type { get; set; } + [JsonPropertyName("type")] public string? Type { get; set; } } public class Attachment { - [JsonPropertyName("id")] public string Id { get; set; } + [JsonPropertyName("id")] public string? Id { get; set; } - [JsonPropertyName("filename")] public string Filename { get; set; } + [JsonPropertyName("filename")] public string? Filename { get; set; } - [JsonPropertyName("contentType")] public string ContentType { get; set; } + [JsonPropertyName("contentType")] public string? ContentType { get; set; } - [JsonPropertyName("size")] public long Size { get; set; } + [JsonPropertyName("size")] public long? Size { get; set; } } public class GroupV2Info { - [JsonPropertyName("id")] public string Id { get; set; } + [JsonPropertyName("id")] public string? Id { get; set; } - [JsonPropertyName("name")] public string Name { get; set; } + [JsonPropertyName("name")] public string? Name { get; set; } - [JsonPropertyName("revision")] public int Revision { get; set; } + [JsonPropertyName("revision")] public int? Revision { get; set; } } public class ReactionData { - [JsonPropertyName("emoji")] public string Emoji { get; set; } + [JsonPropertyName("emoji")] public string? Emoji { get; set; } - [JsonPropertyName("remove")] public bool Remove { get; set; } + [JsonPropertyName("remove")] public bool? Remove { get; set; } - [JsonPropertyName("targetAuthor")] public string TargetAuthor { get; set; } + [JsonPropertyName("targetAuthor")] public string? TargetAuthor { get; set; } [JsonPropertyName("targetSentTimestamp")] - public long TargetSentTimestamp { get; set; } + public long? TargetSentTimestamp { get; set; } } public class Mention { - [JsonPropertyName("start")] public int Start { get; set; } + [JsonPropertyName("start")] public int? Start { get; set; } - [JsonPropertyName("length")] public int Length { get; set; } + [JsonPropertyName("length")] public int? Length { get; set; } - [JsonPropertyName("uuid")] public string Uuid { get; set; } + [JsonPropertyName("uuid")] public string? Uuid { get; set; } } public class QuoteData { - [JsonPropertyName("id")] public long Id { get; set; } + [JsonPropertyName("id")] public long? Id { get; set; } - [JsonPropertyName("author")] public string Author { get; set; } + [JsonPropertyName("author")] public string? Author { get; set; } - [JsonPropertyName("text")] public string Text { get; set; } + [JsonPropertyName("text")] public string? Text { get; set; } - [JsonPropertyName("timestamp")] public long Timestamp { get; set; } + [JsonPropertyName("timestamp")] public long? Timestamp { get; set; } } public class ReadMessage { - [JsonPropertyName("sender")] public string Sender { get; set; } + [JsonPropertyName("sender")] public string? Sender { get; set; } - [JsonPropertyName("timestamp")] public long Timestamp { get; set; } + [JsonPropertyName("timestamp")] public long? Timestamp { get; set; } } public class PreviewData { - [JsonPropertyName("url")] public string Url { get; set; } + [JsonPropertyName("url")] public string? Url { get; set; } - [JsonPropertyName("title")] public string Title { get; set; } + [JsonPropertyName("title")] public string? Title { get; set; } - [JsonPropertyName("description")] public string Description { get; set; } + [JsonPropertyName("description")] public string? Description { get; set; } [JsonPropertyName("image")] public Attachment? Image { get; set; } } \ No newline at end of file diff --git a/src/global.json b/src/global.json index 1335fb8..bd3e64f 100644 --- a/src/global.json +++ b/src/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.102", + "version": "10.0.*", "rollForward": "latestMajor", "allowPrerelease": false }