diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml new file mode 100644 index 00000000..f8822cea --- /dev/null +++ b/.github/workflows/jekyll-gh-pages.yml @@ -0,0 +1,51 @@ +# Sample workflow for building and deploying a Jekyll site to GitHub Pages +name: Deploy Jekyll with GitHub Pages dependencies preinstalled + +on: + # Runs on pushes targeting the default branch + push: + branches: ["develop"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./ + destination: ./_site + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.idea/.idea.Botticelli/.idea/vcs.xml b/.idea/.idea.Botticelli/.idea/vcs.xml index 3672fbf7..9d91c1f7 100644 --- a/.idea/.idea.Botticelli/.idea/vcs.xml +++ b/.idea/.idea.Botticelli/.idea/vcs.xml @@ -3,5 +3,6 @@ + \ No newline at end of file diff --git a/Botticelli.AI.ChatGpt/Provider/ChatGptProvider.cs b/Botticelli.AI.ChatGpt/Provider/ChatGptProvider.cs index f8b72f5e..9a963eb1 100644 --- a/Botticelli.AI.ChatGpt/Provider/ChatGptProvider.cs +++ b/Botticelli.AI.ChatGpt/Provider/ChatGptProvider.cs @@ -54,7 +54,7 @@ protected override async Task ProcessGptResponse(AiMessage message, text.AppendJoin(' ', part?.Choices? .Select(c => (c.Message ?? c.Delta)?.Content) ?? - Array.Empty()); + []); var responseMessage = new SendMessageResponse(message.Uid) { @@ -99,14 +99,14 @@ protected override async Task GetGptResponse(AiMessage mess var content = JsonContent.Create(new ChatGptInputMessage { Model = Settings.Value.Model, - Messages = new List - { + Messages = + [ new() { Role = "user", Content = message.Body } - }, + ], Temperature = Settings.Value.Temperature, Stream = Settings.Value.StreamGeneration }); diff --git a/Botticelli.AI.DeepSeekGpt/Extensions/ServiceCollectionExtensions.cs b/Botticelli.AI.DeepSeekGpt/Extensions/ServiceCollectionExtensions.cs index ca197523..dc14a82f 100644 --- a/Botticelli.AI.DeepSeekGpt/Extensions/ServiceCollectionExtensions.cs +++ b/Botticelli.AI.DeepSeekGpt/Extensions/ServiceCollectionExtensions.cs @@ -33,7 +33,8 @@ public static IServiceCollection AddDeepSeekProvider(this IServiceCollection ser s.Instruction = deepSeekGptSettings.Instruction; }); - services.AddSingleton(); + services.AddSingleton() + .AddHttpClient(); return services; } diff --git a/Botticelli.AI.DeepSeekGpt/Provider/DeepSeekGptProvider.cs b/Botticelli.AI.DeepSeekGpt/Provider/DeepSeekGptProvider.cs index 273701b9..91e2e027 100644 --- a/Botticelli.AI.DeepSeekGpt/Provider/DeepSeekGptProvider.cs +++ b/Botticelli.AI.DeepSeekGpt/Provider/DeepSeekGptProvider.cs @@ -67,19 +67,20 @@ protected override async Task GetGptResponse(AiMessage mess { Model = Settings.Value.Model, MaxTokens = Settings.Value.MaxTokens, - Messages = new List - { + Messages = + [ new() { Role = SystemRole, Content = Settings.Value.Instruction }, + new() { Role = UserRole, Content = message.Body } - } + ] }; deepSeekGptMessage.Messages.AddRange(message.AdditionalMessages?.Select(m => new DeepSeekInnerInputMessage @@ -91,7 +92,7 @@ protected override async Task GetGptResponse(AiMessage mess var content = JsonContent.Create(deepSeekGptMessage); - Logger.LogDebug($"{nameof(SendAsync)}({message.ChatIds}) content: {content.Value}"); + Logger.LogDebug("{SendAsyncName}({MessageChatIds}) content: {ContentValue}", nameof(SendAsync), message.ChatIds, content.Value); return await client.PostAsync(Completion, content, diff --git a/Botticelli.AI.GptJ/Provider/GptJProvider.cs b/Botticelli.AI.GptJ/Provider/GptJProvider.cs index 49e671e6..05bc074d 100644 --- a/Botticelli.AI.GptJ/Provider/GptJProvider.cs +++ b/Botticelli.AI.GptJ/Provider/GptJProvider.cs @@ -71,7 +71,7 @@ protected override async Task GetGptResponse(AiMessage mess Temperature = Settings.Value.Temperature }); - Logger.LogDebug($"{nameof(SendAsync)}({message.ChatIds}) content: {content.Value}"); + Logger.LogDebug("{SendAsyncName}({MessageChatIds}) content: {ContentValue}", nameof(SendAsync), message.ChatIds, content.Value); return await client.PostAsync(Url.Combine($"{Settings.Value.Url}", "generate"), content, diff --git a/Botticelli.AI.Test/AIProvider/BaseAiProviderTest.cs b/Botticelli.AI.Test/AIProvider/BaseAiProviderTest.cs index 682261a9..a292eb26 100644 --- a/Botticelli.AI.Test/AIProvider/BaseAiProviderTest.cs +++ b/Botticelli.AI.Test/AIProvider/BaseAiProviderTest.cs @@ -12,7 +12,7 @@ using FluentAssertions; using FluentValidation; using NUnit.Framework; -using Shared; +using Mocks; using WireMock.Server; namespace Botticelli.AI.Test.AIProvider; diff --git a/Botticelli.AI.Test/AIProvider/ChatGptProviderTest.cs b/Botticelli.AI.Test/AIProvider/ChatGptProviderTest.cs index bc9704a2..bce0821f 100644 --- a/Botticelli.AI.Test/AIProvider/ChatGptProviderTest.cs +++ b/Botticelli.AI.Test/AIProvider/ChatGptProviderTest.cs @@ -5,7 +5,7 @@ using Botticelli.AI.ChatGpt.Provider; using Botticelli.AI.ChatGpt.Settings; using NUnit.Framework; -using Shared; +using Mocks; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using Usage = Botticelli.AI.ChatGpt.Message.ChatGpt.Usage; diff --git a/Botticelli.AI.Test/AIProvider/DeepSeekGptProviderTest.cs b/Botticelli.AI.Test/AIProvider/DeepSeekGptProviderTest.cs index 0d00c1ee..b643ff5f 100644 --- a/Botticelli.AI.Test/AIProvider/DeepSeekGptProviderTest.cs +++ b/Botticelli.AI.Test/AIProvider/DeepSeekGptProviderTest.cs @@ -5,7 +5,7 @@ using Botticelli.AI.DeepSeekGpt.Provider; using Botticelli.AI.DeepSeekGpt.Settings; using NUnit.Framework; -using Shared; +using Mocks; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using Usage = Botticelli.AI.DeepSeekGpt.Message.DeepSeek.Usage; diff --git a/Botticelli.AI.Test/AIProvider/YaGptProviderTest.cs b/Botticelli.AI.Test/AIProvider/YaGptProviderTest.cs index 1f0c44af..94181674 100644 --- a/Botticelli.AI.Test/AIProvider/YaGptProviderTest.cs +++ b/Botticelli.AI.Test/AIProvider/YaGptProviderTest.cs @@ -4,7 +4,7 @@ using Botticelli.AI.YaGpt.Provider; using Botticelli.AI.YaGpt.Settings; using NUnit.Framework; -using Shared; +using Mocks; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; diff --git a/Botticelli.AI.Test/Botticelli.AI.Test.csproj b/Botticelli.AI.Test/Botticelli.AI.Test.csproj index 2af7dd51..93f89488 100644 --- a/Botticelli.AI.Test/Botticelli.AI.Test.csproj +++ b/Botticelli.AI.Test/Botticelli.AI.Test.csproj @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/Botticelli.AI.YaGpt/Provider/YaGptProvider.cs b/Botticelli.AI.YaGpt/Provider/YaGptProvider.cs index 8b3e9820..ed84c8f0 100644 --- a/Botticelli.AI.YaGpt/Provider/YaGptProvider.cs +++ b/Botticelli.AI.YaGpt/Provider/YaGptProvider.cs @@ -89,19 +89,20 @@ protected override async Task GetGptResponse(AiMessage mess var yaGptMessage = new YaGptInputMessage { ModelUri = Settings.Value.Model, - Messages = new List - { + Messages = + [ new() { Role = SystemRole, Text = Settings.Value.Instruction }, + new() { Role = UserRole, Text = message.Body } - }, + ], CompletionOptions = new CompletionOptions { MaxTokens = Settings.Value.MaxTokens, @@ -119,7 +120,7 @@ protected override async Task GetGptResponse(AiMessage mess var content = JsonContent.Create(yaGptMessage); - Logger.LogDebug($"{nameof(SendAsync)}({message.ChatIds}) content: {content.Value}"); + Logger.LogDebug("{SendAsyncName}({MessageChatIds}) content: {ContentValue}", nameof(SendAsync), message.ChatIds, content.Value); return await client.PostAsync(Url.Combine($"{Settings.Value.Url}", Completion), content, diff --git a/Botticelli.AI/AIProvider/ChatGptProvider.cs b/Botticelli.AI/AIProvider/ChatGptProvider.cs index c7f2f137..34171eaf 100644 --- a/Botticelli.AI/AIProvider/ChatGptProvider.cs +++ b/Botticelli.AI/AIProvider/ChatGptProvider.cs @@ -37,7 +37,7 @@ public virtual async Task SendAsync(AiMessage message, CancellationToken token) try { - Logger.LogDebug($"{nameof(SendAsync)}({message.ChatIds}) started"); + Logger.LogDebug("{SendAsyncName}({MessageChatIds}) started", nameof(SendAsync), message.ChatIds); using var client = GetClient(); @@ -53,7 +53,7 @@ public virtual async Task SendAsync(AiMessage message, CancellationToken token) await SendErrorGptResponse(message, reason, token); } - Logger.LogDebug($"{nameof(SendAsync)}({message.ChatIds}) finished"); + Logger.LogDebug("{SendAsyncName}({MessageChatIds}) finished", nameof(SendAsync), message.ChatIds); } catch (Exception ex) { diff --git a/Botticelli.AI/Botticelli.AI.csproj b/Botticelli.AI/Botticelli.AI.csproj index c714f90a..7d5c957d 100644 --- a/Botticelli.AI/Botticelli.AI.csproj +++ b/Botticelli.AI/Botticelli.AI.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli diff --git a/Botticelli.Analytics.Shared/Botticelli.Analytics.Shared.csproj b/Botticelli.Analytics.Shared/Botticelli.Analytics.Shared.csproj index 246d9e9c..e9231fbc 100644 --- a/Botticelli.Analytics.Shared/Botticelli.Analytics.Shared.csproj +++ b/Botticelli.Analytics.Shared/Botticelli.Analytics.Shared.csproj @@ -5,7 +5,7 @@ enable enable true - 0.7.0 + 0.8.0 https://botticellibots.com new_logo_compact.png https://github.com/devgopher/botticelli @@ -14,11 +14,7 @@ - - True - - new_logo_compact.png - + diff --git a/Botticelli.Audio/Botticelli.Audio.csproj b/Botticelli.Audio/Botticelli.Audio.csproj index c324d600..6bf41b03 100644 --- a/Botticelli.Audio/Botticelli.Audio.csproj +++ b/Botticelli.Audio/Botticelli.Audio.csproj @@ -5,7 +5,7 @@ enable enable true - 0.7.0 + 0.8.0 Botticelli.Audio BotticelliBots new_logo_compact.png diff --git a/Botticelli.Audio/UniversalLowQualityConvertor.cs b/Botticelli.Audio/UniversalLowQualityConvertor.cs index cfe8a8f4..70b9554f 100644 --- a/Botticelli.Audio/UniversalLowQualityConvertor.cs +++ b/Botticelli.Audio/UniversalLowQualityConvertor.cs @@ -35,7 +35,7 @@ public byte[] Convert(Stream input, AudioInfo tgtParams) } catch (Exception ex) { - _logger.LogError($"{nameof(Convert)} => ({tgtParams.AudioFormat}, {tgtParams.Bitrate}) error", ex); + _logger.LogError("{ConvertName} => ({TgtParamsAudioFormat}, {TgtParamsBitrate}) error: {Ex}!", nameof(Convert), tgtParams.AudioFormat, tgtParams.Bitrate, ex); throw new AudioConvertorException($"Audio conversion error: {ex.Message}", ex); } @@ -94,9 +94,9 @@ private byte[] ProcessByStreamEncoder(Stream input, AudioInfo tgtParams) } catch (IOException ex) { - _logger.LogError($"{nameof(Convert)} => ({tgtParams.AudioFormat}, {tgtParams.Bitrate}) error", ex); + _logger.LogError("{ConvertName} => ({TgtParamsAudioFormat}, {TgtParamsBitrate}) error", nameof(Convert), tgtParams.AudioFormat, tgtParams.Bitrate, ex); - return Array.Empty(); + return []; } } diff --git a/Botticelli.Auth.Data.Postgres/Botticelli.Auth.Data.Postgres.csproj b/Botticelli.Auth.Data.Postgres/Botticelli.Auth.Data.Postgres.csproj index 2a49e8d4..ec092d5e 100644 --- a/Botticelli.Auth.Data.Postgres/Botticelli.Auth.Data.Postgres.csproj +++ b/Botticelli.Auth.Data.Postgres/Botticelli.Auth.Data.Postgres.csproj @@ -7,10 +7,10 @@ - + - + diff --git a/Botticelli.Auth.Data.Postgres/Migrations/20250219145627_auth.Designer.cs b/Botticelli.Auth.Data.Postgres/Migrations/20250219145627_auth.Designer.cs index 4047640a..1bb7087f 100644 --- a/Botticelli.Auth.Data.Postgres/Migrations/20250219145627_auth.Designer.cs +++ b/Botticelli.Auth.Data.Postgres/Migrations/20250219145627_auth.Designer.cs @@ -20,7 +20,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Botticelli.Auth.Sample.Telegram") - .HasAnnotation("ProductVersion", "8.0.13") + .HasAnnotation("ProductVersion", "8.0.16") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); diff --git a/Botticelli.Auth.Data.Postgres/Migrations/AuthDefaultDbContextModelSnapshot.cs b/Botticelli.Auth.Data.Postgres/Migrations/AuthDefaultDbContextModelSnapshot.cs index b8417113..ca08ef9c 100644 --- a/Botticelli.Auth.Data.Postgres/Migrations/AuthDefaultDbContextModelSnapshot.cs +++ b/Botticelli.Auth.Data.Postgres/Migrations/AuthDefaultDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Botticelli.Auth.Sample.Telegram") - .HasAnnotation("ProductVersion", "8.0.13") + .HasAnnotation("ProductVersion", "8.0.16") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); diff --git a/Botticelli.Auth.Data.Sqlite/Botticelli.Auth.Data.Sqlite.csproj b/Botticelli.Auth.Data.Sqlite/Botticelli.Auth.Data.Sqlite.csproj index 92355e04..74200a0f 100644 --- a/Botticelli.Auth.Data.Sqlite/Botticelli.Auth.Data.Sqlite.csproj +++ b/Botticelli.Auth.Data.Sqlite/Botticelli.Auth.Data.Sqlite.csproj @@ -7,7 +7,7 @@ - + diff --git a/Botticelli.Auth.Data.Sqlite/Migrations/20250308203827_auth.Designer.cs b/Botticelli.Auth.Data.Sqlite/Migrations/20250308203827_auth.Designer.cs index 19efc2e2..bd98dc50 100644 --- a/Botticelli.Auth.Data.Sqlite/Migrations/20250308203827_auth.Designer.cs +++ b/Botticelli.Auth.Data.Sqlite/Migrations/20250308203827_auth.Designer.cs @@ -20,7 +20,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Botticelli.Auth.Sample.Telegram") - .HasAnnotation("ProductVersion", "8.0.13"); + .HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.Auth.Data.Models.AccessHistory", b => { diff --git a/Botticelli.Auth.Data.Sqlite/Migrations/AuthDefaultDbContextModelSnapshot.cs b/Botticelli.Auth.Data.Sqlite/Migrations/AuthDefaultDbContextModelSnapshot.cs index 1dde8221..6280387b 100644 --- a/Botticelli.Auth.Data.Sqlite/Migrations/AuthDefaultDbContextModelSnapshot.cs +++ b/Botticelli.Auth.Data.Sqlite/Migrations/AuthDefaultDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Botticelli.Auth.Sample.Telegram") - .HasAnnotation("ProductVersion", "8.0.13"); + .HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.Auth.Data.Models.AccessHistory", b => { diff --git a/Botticelli.Auth.Data/Botticelli.Auth.Data.csproj b/Botticelli.Auth.Data/Botticelli.Auth.Data.csproj index c02925c0..9f5909ed 100644 --- a/Botticelli.Auth.Data/Botticelli.Auth.Data.csproj +++ b/Botticelli.Auth.Data/Botticelli.Auth.Data.csproj @@ -8,11 +8,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Botticelli.Auth/Botticelli.Auth.csproj b/Botticelli.Auth/Botticelli.Auth.csproj index 3cedcc3f..44ac59cf 100644 --- a/Botticelli.Auth/Botticelli.Auth.csproj +++ b/Botticelli.Auth/Botticelli.Auth.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/Botticelli.Bot.Dal/BotInfoContext.cs b/Botticelli.Bot.Dal/BotInfoContext.cs index 49d3c579..9970a5d2 100644 --- a/Botticelli.Bot.Dal/BotInfoContext.cs +++ b/Botticelli.Bot.Dal/BotInfoContext.cs @@ -6,12 +6,7 @@ namespace Botticelli.Bot.Data; public class BotInfoContext : DbContext { - // public BotInfoContext() : base((new DbContextOptionsBuilder().UseSqlite("Data Source=database.db")).Options) - // { - // - // } - - public BotInfoContext(DbContextOptions options) : base(options) + public BotInfoContext(DbContextOptions options) : base(options) { } diff --git a/Botticelli.Bot.Dal/Botticelli.Bot.Dal.csproj b/Botticelli.Bot.Dal/Botticelli.Bot.Dal.csproj index 977b158f..ce2bf8c7 100644 --- a/Botticelli.Bot.Dal/Botticelli.Bot.Dal.csproj +++ b/Botticelli.Bot.Dal/Botticelli.Bot.Dal.csproj @@ -8,13 +8,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Botticelli.Bot.Dal/Migrations/20241021193257_botdata.Designer.cs b/Botticelli.Bot.Dal/Migrations/20241021193257_botdata.Designer.cs index b7e92929..d7913e2e 100644 --- a/Botticelli.Bot.Dal/Migrations/20241021193257_botdata.Designer.cs +++ b/Botticelli.Bot.Dal/Migrations/20241021193257_botdata.Designer.cs @@ -17,7 +17,7 @@ partial class botdata protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.Bot.Data.Entities.Bot.BotAdditionalInfo", b => { diff --git a/Botticelli.Bot.Dal/Migrations/20241128210954_BroadCasting.Designer.cs b/Botticelli.Bot.Dal/Migrations/20241128210954_BroadCasting.Designer.cs index 8a38881d..0c86605a 100644 --- a/Botticelli.Bot.Dal/Migrations/20241128210954_BroadCasting.Designer.cs +++ b/Botticelli.Bot.Dal/Migrations/20241128210954_BroadCasting.Designer.cs @@ -17,7 +17,7 @@ partial class BroadCasting protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.BotData.Entities.Bot.BotAdditionalInfo", b => { diff --git a/Botticelli.Bot.Dal/Migrations/BotInfoContextModelSnapshot.cs b/Botticelli.Bot.Dal/Migrations/BotInfoContextModelSnapshot.cs index 37a4877d..46dd44c5 100644 --- a/Botticelli.Bot.Dal/Migrations/BotInfoContextModelSnapshot.cs +++ b/Botticelli.Bot.Dal/Migrations/BotInfoContextModelSnapshot.cs @@ -14,7 +14,7 @@ partial class BotInfoContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.BotData.Entities.Bot.BotAdditionalInfo", b => { diff --git a/Botticelli.Bot.Interfaces/Botticelli.Bot.Interfaces.csproj b/Botticelli.Bot.Interfaces/Botticelli.Bot.Interfaces.csproj index e8c1c898..b7e41fee 100644 --- a/Botticelli.Bot.Interfaces/Botticelli.Bot.Interfaces.csproj +++ b/Botticelli.Bot.Interfaces/Botticelli.Bot.Interfaces.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli diff --git a/Botticelli.Bot.Interfaces/Client/IBusClient.cs b/Botticelli.Bot.Interfaces/Client/IBusClient.cs index 47337bc6..353c64d2 100644 --- a/Botticelli.Bot.Interfaces/Client/IBusClient.cs +++ b/Botticelli.Bot.Interfaces/Client/IBusClient.cs @@ -12,7 +12,7 @@ public IAsyncEnumerable SendAndGetResponseSeries(SendMessag CancellationToken token); public Task SendAndGetResponse(SendMessageRequest request, - CancellationToken token); + CancellationToken token); public Task SendResponse(SendMessageResponse response, CancellationToken token); } \ No newline at end of file diff --git a/Botticelli.Broadcasting.Dal/Botticelli.Broadcasting.Dal.csproj b/Botticelli.Broadcasting.Dal/Botticelli.Broadcasting.Dal.csproj new file mode 100644 index 00000000..1bfdde6d --- /dev/null +++ b/Botticelli.Broadcasting.Dal/Botticelli.Broadcasting.Dal.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/Botticelli.Broadcasting.Dal/BroadcastingContext.cs b/Botticelli.Broadcasting.Dal/BroadcastingContext.cs new file mode 100644 index 00000000..3271a44d --- /dev/null +++ b/Botticelli.Broadcasting.Dal/BroadcastingContext.cs @@ -0,0 +1,27 @@ +using Botticelli.Broadcasting.Dal.Models; +using Microsoft.EntityFrameworkCore; + +namespace Botticelli.Broadcasting.Dal; + +public class BroadcastingContext : DbContext +{ + public DbSet Chats { get; set; } + + public BroadcastingContext() + { + + } + + public BroadcastingContext(DbContextOptions options) : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .HasKey(k => new { k.ChatId, k.MessageId }); + } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting.Dal/Migrations/20250603202442_initial.Designer.cs b/Botticelli.Broadcasting.Dal/Migrations/20250603202442_initial.Designer.cs new file mode 100644 index 00000000..22956f56 --- /dev/null +++ b/Botticelli.Broadcasting.Dal/Migrations/20250603202442_initial.Designer.cs @@ -0,0 +1,75 @@ +// +using System; +using Botticelli.Broadcasting.Dal; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Botticelli.Broadcasting.Dal.Migrations +{ + [DbContext(typeof(BroadcastingContext))] + [Migration("20250603202442_initial")] + partial class initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); + + modelBuilder.Entity("Botticelli.Broadcasting.Dal.Models.Chat", b => + { + b.Property("ChatId") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.HasKey("ChatId"); + + b.ToTable("Chats"); + }); + + modelBuilder.Entity("Botticelli.Broadcasting.Dal.Models.MessageCache", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("SerializedMessageObject") + .IsRequired() + .HasMaxLength(100000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MessageCaches"); + }); + + modelBuilder.Entity("Botticelli.Broadcasting.Dal.Models.MessageStatus", b => + { + b.Property("ChatId") + .HasColumnType("TEXT"); + + b.Property("MessageId") + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("IsSent") + .HasColumnType("INTEGER"); + + b.Property("SentDate") + .HasColumnType("TEXT"); + + b.HasKey("ChatId", "MessageId"); + + b.ToTable("MessageStatuses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Botticelli.Server.Data/Migrations/20230112102521_init.cs b/Botticelli.Broadcasting.Dal/Migrations/20250603202442_initial.cs similarity index 51% rename from Botticelli.Server.Data/Migrations/20230112102521_init.cs rename to Botticelli.Broadcasting.Dal/Migrations/20250603202442_initial.cs index a67866c2..5df3d4ad 100644 --- a/Botticelli.Server.Data/Migrations/20230112102521_init.cs +++ b/Botticelli.Broadcasting.Dal/Migrations/20250603202442_initial.cs @@ -3,26 +3,24 @@ #nullable disable -namespace Botticelli.Server.Data.Migrations +namespace Botticelli.Broadcasting.Dal.Migrations { /// - public partial class init : Migration + public partial class initial : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "BotInfo", + name: "Chats", columns: table => new { - BotId = table.Column(type: "TEXT", nullable: false), - BotName = table.Column(type: "TEXT", nullable: true), - LastKeepAlive = table.Column(type: "TEXT", nullable: true), - Status = table.Column(type: "INTEGER", nullable: true) + ChatId = table.Column(type: "TEXT", nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_BotInfo", x => x.BotId); + table.PrimaryKey("PK_Chats", x => x.ChatId); }); } @@ -30,7 +28,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "BotInfo"); + name: "Chats"); } } } diff --git a/Botticelli.Broadcasting.Dal/Migrations/BroadcastingContextModelSnapshot.cs b/Botticelli.Broadcasting.Dal/Migrations/BroadcastingContextModelSnapshot.cs new file mode 100644 index 00000000..d4225d22 --- /dev/null +++ b/Botticelli.Broadcasting.Dal/Migrations/BroadcastingContextModelSnapshot.cs @@ -0,0 +1,35 @@ +// +using System; +using Botticelli.Broadcasting.Dal; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Botticelli.Broadcasting.Dal.Migrations +{ + [DbContext(typeof(BroadcastingContext))] + partial class BroadcastingContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); + + modelBuilder.Entity("Botticelli.Broadcasting.Dal.Models.Chat", b => + { + b.Property("ChatId") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.HasKey("ChatId"); + + b.ToTable("Chats"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Botticelli.Broadcasting.Dal/Models/Chat.cs b/Botticelli.Broadcasting.Dal/Models/Chat.cs new file mode 100644 index 00000000..c6cf15ae --- /dev/null +++ b/Botticelli.Broadcasting.Dal/Models/Chat.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; + +namespace Botticelli.Broadcasting.Dal.Models; + +/// +/// Represents a chat record in the system. +/// This record stores information about a specific chat session, including its status. +/// +public record Chat +{ + /// + /// Gets or sets the unique identifier for the chat. + /// This property serves as the primary key in the database. + /// + [Key] + public required string ChatId { get; set; } + + /// + /// Gets or sets a value indicating whether the chat is currently active. + /// + public bool IsActive { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting.Dal/Models/MessageCache.cs b/Botticelli.Broadcasting.Dal/Models/MessageCache.cs new file mode 100644 index 00000000..7037db42 --- /dev/null +++ b/Botticelli.Broadcasting.Dal/Models/MessageCache.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; + +namespace Botticelli.Broadcasting.Dal.Models; + +/// +/// Represents a cache for serialized messages. +/// This class is used to store messages in a serialized format for efficient retrieval. +/// +public class MessageCache +{ + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier for the message cache entry. + /// The serialized message object. + public MessageCache(string id, string serializedMessageObject) + { + Id = id; + SerializedMessageObject = serializedMessageObject; + } + + /// + /// Gets or sets the unique identifier for the message cache entry. + /// This property serves as the primary key in the database. + /// + [Key] + public required string Id { get; set; } + + /// + /// Gets or sets the serialized representation of the message object. + /// This property stores the message in a format such as JSON or XML. + /// + [MaxLength(100000)] + public required string SerializedMessageObject { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting.Dal/Models/MessageStatus.cs b/Botticelli.Broadcasting.Dal/Models/MessageStatus.cs new file mode 100644 index 00000000..29695b48 --- /dev/null +++ b/Botticelli.Broadcasting.Dal/Models/MessageStatus.cs @@ -0,0 +1,39 @@ +namespace Botticelli.Broadcasting.Dal.Models; + +/// +/// Represents the status of a message within a chat. +/// This class stores information about whether a message has been sent, +/// along with relevant timestamps and identifiers. +/// +public class MessageStatus +{ + /// + /// Gets or sets the unique identifier for the message. + /// This property is used to associate the status with a specific message. + /// + public required string MessageId { get; set; } + + /// + /// Gets or sets the unique identifier for the chat. + /// This property links the message status to a specific chat session. + /// + public required string ChatId { get; set; } + + /// + /// Gets or sets a value indicating whether the message has been sent. + /// This property indicates the delivery status of the message (true if sent, false otherwise). + /// + public required bool IsSent { get; set; } + + /// + /// Gets or sets the date and time when the message status was created. + /// This property records when the status entry was created in the system. + /// + public required DateTime CreatedDate { get; set; } + + /// + /// Gets or sets the date and time when the message was sent. + /// This property is nullable, as it may not be set if the message has not been sent yet. + /// + public DateTime? SentDate { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting.Shared/Botticelli.Broadcasting.Shared.csproj b/Botticelli.Broadcasting.Shared/Botticelli.Broadcasting.Shared.csproj new file mode 100644 index 00000000..e1ea3929 --- /dev/null +++ b/Botticelli.Broadcasting.Shared/Botticelli.Broadcasting.Shared.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Botticelli.Broadcasting.Shared/Requests/GetBroadcastMessagesRequest.cs b/Botticelli.Broadcasting.Shared/Requests/GetBroadcastMessagesRequest.cs new file mode 100644 index 00000000..9680a1fe --- /dev/null +++ b/Botticelli.Broadcasting.Shared/Requests/GetBroadcastMessagesRequest.cs @@ -0,0 +1,7 @@ +namespace Botticelli.Broadcasting.Shared.Requests; + +public class GetBroadcastMessagesRequest +{ + public required string BotId { get; set; } + public required TimeSpan HowOld { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting.Shared/Responses/GetBroadcastMessagesResponse.cs b/Botticelli.Broadcasting.Shared/Responses/GetBroadcastMessagesResponse.cs new file mode 100644 index 00000000..80f8e539 --- /dev/null +++ b/Botticelli.Broadcasting.Shared/Responses/GetBroadcastMessagesResponse.cs @@ -0,0 +1,9 @@ +using Botticelli.Shared.ValueObjects; + +namespace Botticelli.Broadcasting.Shared.Responses; + +public class GetBroadcastMessagesResponse +{ + public required string Id { get; set; } + public required List Messages { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting.Telegram/Botticelli.Broadcasting.Telegram.csproj b/Botticelli.Broadcasting.Telegram/Botticelli.Broadcasting.Telegram.csproj new file mode 100644 index 00000000..baa5e7cc --- /dev/null +++ b/Botticelli.Broadcasting.Telegram/Botticelli.Broadcasting.Telegram.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Botticelli.Broadcasting.Telegram/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Broadcasting.Telegram/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..777cfe1d --- /dev/null +++ b/Botticelli.Broadcasting.Telegram/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,46 @@ +using Botticelli.Broadcasting.Extensions; +using Botticelli.Framework.Builders; +using Botticelli.Framework.Telegram; +using Botticelli.Framework.Telegram.Builders; +using Microsoft.Extensions.Configuration; + +namespace Botticelli.Broadcasting.Telegram.Extensions; + +public static class ServiceCollectionExtensions +{ + /// + /// Adds broadcasting to a bot + /// + /// + /// + /// + /// + public static TelegramBotBuilder AddBroadcasting(this TelegramBotBuilder botBuilder, + IConfiguration config) + where TBot : TelegramBot + { + var builder = botBuilder as BotBuilder>; + builder!.AddBroadcasting(config); + + return botBuilder; + } + + /// + /// Adds broadcasting to a bot + /// + /// + /// + /// + /// + /// + public static TelegramBotBuilder AddTelegramBroadcasting(this TelegramBotBuilder botBuilder, + IConfiguration config) + where TBot : TelegramBot + where TBotBuilder : BotBuilder + { + var builder = botBuilder as BotBuilder>; + builder!.AddBroadcasting(config); + + return botBuilder; + } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting/Botticelli.Broadcasting.csproj b/Botticelli.Broadcasting/Botticelli.Broadcasting.csproj new file mode 100644 index 00000000..812fb28f --- /dev/null +++ b/Botticelli.Broadcasting/Botticelli.Broadcasting.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/Botticelli.Broadcasting/BroadcastReceiver.cs b/Botticelli.Broadcasting/BroadcastReceiver.cs new file mode 100644 index 00000000..0325752e --- /dev/null +++ b/Botticelli.Broadcasting/BroadcastReceiver.cs @@ -0,0 +1,141 @@ +using System.Net; +using System.Net.Http.Json; +using Botticelli.Broadcasting.Dal; +using Botticelli.Broadcasting.Settings; +using Botticelli.Framework; +using Botticelli.Interfaces; +using Botticelli.Shared.API; +using Botticelli.Shared.API.Client.Requests; +using Botticelli.Shared.API.Client.Responses; +using Flurl.Http; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Polly; + +namespace Botticelli.Broadcasting; + +/// +/// Broadcaster is a service that polls an admin API for new messages +/// to send to various chat clients. It implements IHostedService to manage +/// the lifecycle of the service within a hosted environment. +/// +/// The type of bot that implements IBot interface. +public class BroadcastReceiver : IHostedService + where TBot : BaseBot, IBot +{ + private readonly IBot _bot; + private readonly BroadcastingContext _context; + private readonly TimeSpan _longPollTimeout = TimeSpan.FromSeconds(30); + private readonly TimeSpan _broadcastReceivedTimeout = TimeSpan.FromSeconds(10); + private readonly TimeSpan _retryPause = TimeSpan.FromMilliseconds(150); + private const int RetryCount = 3; + private readonly BroadcastingSettings _settings; + private readonly ILogger> _logger; + public CancellationTokenSource CancellationTokenSource { get; private set; } + + public BroadcastReceiver(IBot bot, + BroadcastingContext context, + BroadcastingSettings settings, + ILogger> logger) + { + _bot = bot; + _settings = settings; + _logger = logger; + _context = context; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + // Polls admin API for new messages to send to our chats and adds them to a MessageCache/MessageStatus + _ = Task.Run(async () => + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + var updates = await GetUpdates(cancellationToken); + + if (updates?.Messages == null) continue; + + foreach (var update in updates.Messages) + { + // if no chat were specified - broadcast on all chats, we've + if (update.ChatIds.Count == 0) + update.ChatIds = _context.Chats.Select(x => x.ChatId).ToList(); + + var request = new SendMessageRequest + { + Message = update + }; + + List messageIds = [update.Uid]; + + var sendMessageResponse = await _bot.SendMessageAsync(request, cancellationToken); + + if (sendMessageResponse.MessageSentStatus != MessageSentStatus.Ok) continue; + + var broadcastResult = await SendBroadcastReceived(messageIds, cancellationToken); + + if (broadcastResult is { IsSuccess: false }) + _logger.LogError("Error sending a BroadcastReceived message!"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + } + + await Task.Delay(_retryPause, cancellationToken); + } + }, + cancellationToken); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + CancellationTokenSource.Cancel(false); + + return Task.CompletedTask; + } + + private async Task GetUpdates(CancellationToken cancellationToken) + { + var updatesResponse = await $"{_settings.ServerUri}/bot/client/GetBroadcast" + .WithTimeout(_longPollTimeout) + .PostJsonAsync(new GetBroadCastMessagesRequest + { + BotId = _settings.BotId + }, cancellationToken: cancellationToken); + + if (!updatesResponse.ResponseMessage.IsSuccessStatusCode) + return null; + + return await updatesResponse.ResponseMessage.Content + .ReadFromJsonAsync( + cancellationToken); + } + + private async Task SendBroadcastReceived(List chatIds, CancellationToken cancellationToken) + { + var response = Policy + .Handle() // Handle network-related exceptions + .OrResult(r => !r.ResponseMessage.IsSuccessStatusCode) // Handle non-success status codes + .WaitAndRetryAsync(RetryCount, i => _retryPause.Multiply(10 * i)) + .ExecuteAsync(async () => await $"{_settings.ServerUri}/bot/client/BroadcastReceived" + .WithTimeout(_broadcastReceivedTimeout) + .PostJsonAsync(new BroadCastMessagesReceivedRequest + { + BotId = _settings.BotId, + MessageIds = chatIds.ToArray() + }, + cancellationToken: cancellationToken)); + + return await response.Result + .ResponseMessage + .Content + .ReadFromJsonAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting/Exceptions/BroadcastingException.cs b/Botticelli.Broadcasting/Exceptions/BroadcastingException.cs new file mode 100644 index 00000000..4d657ef2 --- /dev/null +++ b/Botticelli.Broadcasting/Exceptions/BroadcastingException.cs @@ -0,0 +1,3 @@ +namespace Botticelli.Broadcasting.Exceptions; + +public class BroadcastingException(string message, Exception? ex = null) : Exception(message, ex); \ No newline at end of file diff --git a/Botticelli.Broadcasting/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Broadcasting/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..2d5d44f9 --- /dev/null +++ b/Botticelli.Broadcasting/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,74 @@ +using System.Configuration; +using Botticelli.Broadcasting.Dal; +using Botticelli.Broadcasting.Dal.Models; +using Botticelli.Broadcasting.Settings; +using Botticelli.Framework; +using Botticelli.Framework.Builders; +using Botticelli.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Botticelli.Broadcasting.Extensions; + +public static class ServiceCollectionExtensions +{ + /// + /// Adds broadcasting to a bot + /// + /// + /// + /// + /// + /// + public static BotBuilder AddBroadcasting(this BotBuilder botBuilder, + IConfiguration config) + where TBot : BaseBot, IBot + where TBotBuilder : BotBuilder + { + var settings = config.GetSection(BroadcastingSettings.Section).Get(); + + if (settings == null) throw new ConfigurationErrorsException("Broadcasting settings are missing!"); + + botBuilder.Services + .AddDbContext(opt => opt.UseSqlite($"Data source={settings.ConnectionString}")); + + botBuilder.Services.AddHostedService>(sp => + new BroadcastReceiver(sp.GetRequiredService(), + sp.GetRequiredService(), + settings, + sp.GetRequiredService>>())); + + ApplyMigrations(botBuilder.Services); + + return botBuilder.AddOnMessageReceived((_, args) => + { + var context = botBuilder.Services.BuildServiceProvider().GetRequiredService(); + + var disabledChats = context.Chats.Where(c => !c.IsActive && args.Message.ChatIds.Contains(c.ChatId)) + .AsQueryable(); + var nonExistingChats = args.Message.ChatIds.Where(c => context.Chats.All(cc => cc.ChatId != c)) + .Select(c => new Chat + { + ChatId = c, + IsActive = true + }) + .ToList(); + + context.Chats.AddRange(nonExistingChats); + disabledChats.ExecuteUpdate(c => c.SetProperty(chat => chat.IsActive, true)); + + context.SaveChanges(); + }); + } + + private static void ApplyMigrations(IServiceCollection services) + { + var sp = services.BuildServiceProvider(); + var context = sp.GetRequiredService(); + + if (context.Database.EnsureCreated()) + context.Database.Migrate(); + } +} \ No newline at end of file diff --git a/Botticelli.Broadcasting/Settings/BroadcastingSettings.cs b/Botticelli.Broadcasting/Settings/BroadcastingSettings.cs new file mode 100644 index 00000000..9e19dde0 --- /dev/null +++ b/Botticelli.Broadcasting/Settings/BroadcastingSettings.cs @@ -0,0 +1,10 @@ +namespace Botticelli.Broadcasting.Settings; + +public class BroadcastingSettings +{ + public const string Section = "Broadcasting"; + public required string BotId { get; set; } + public TimeSpan? HowOld { get; set; } = TimeSpan.FromSeconds(60); + public required string ServerUri { get; set; } + public required string ConnectionString { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Bus.Rabbit/Agent/RabbitAgent.cs b/Botticelli.Bus.Rabbit/Agent/RabbitAgent.cs index e56e3c9d..c8d7be38 100644 --- a/Botticelli.Bus.Rabbit/Agent/RabbitAgent.cs +++ b/Botticelli.Bus.Rabbit/Agent/RabbitAgent.cs @@ -54,18 +54,18 @@ public async Task SendResponseAsync(SendMessageResponse response, { try { - _logger.LogDebug($"{nameof(SendResponseAsync)}({response.Uid}) start..."); + _logger.LogDebug("{SendResponseAsyncName}({ResponseUid}) start...", nameof(SendResponseAsync), response.Uid); var policy = Policy.Handle() .WaitAndRetryAsync(5, n => TimeSpan.FromSeconds(3 * Math.Exp(n))); await policy.ExecuteAsync(() => InnerSend(response)); - _logger.LogDebug($"{nameof(SendResponseAsync)}({response.Uid}) finished"); + _logger.LogDebug("{SendResponseAsyncName}({ResponseUid}) finished", nameof(SendResponseAsync), response.Uid); } catch (Exception ex) { - _logger.LogError(ex, $"Error sending a response: {ex.Message}"); + _logger.LogError(ex, "Error sending a response: {ExMessage}", ex.Message); } } @@ -79,6 +79,7 @@ public Task StopAsync(CancellationToken cancellationToken) { _isActive = false; Thread.Sleep(3000); + return Task.CompletedTask; } @@ -87,10 +88,11 @@ public Task StopAsync(CancellationToken cancellationToken) /// public Task Subscribe(CancellationToken token) { - _logger.LogDebug($"{nameof(Subscribe)}({typeof(THandler).Name}) start..."); + _logger.LogDebug("{SubscribeName}({Name}) start...", nameof(Subscribe), typeof(THandler).Name); var handler = _sp.GetRequiredService(); ProcessSubscription(token, handler); + return Task.CompletedTask; } @@ -101,11 +103,9 @@ private void ProcessSubscription(CancellationToken token, THandler handler) var connection = _rabbitConnectionFactory.CreateConnection(); var channel = connection.CreateModel(); var queue = GetRequestQueueName(); - var declareResult = _settings.QueueSettings is { TryCreate: true } - ? channel.QueueDeclare(queue, _settings.QueueSettings.Durable, false) - : channel.QueueDeclarePassive(queue); + var declareResult = _settings.QueueSettings is {TryCreate: true} ? channel.QueueDeclare(queue, _settings.QueueSettings.Durable, false) : channel.QueueDeclarePassive(queue); - _logger.LogDebug($"{nameof(Subscribe)}({typeof(THandler).Name}) queue declare: {declareResult.QueueName}"); + _logger.LogDebug("{SubscribeName}({Name}) queue declare: {DeclareResultQueueName}", nameof(Subscribe), typeof(THandler).Name, declareResult.QueueName); _consumer = new EventingBasicConsumer(channel); @@ -125,8 +125,7 @@ private void ProcessSubscription(CancellationToken token, THandler handler) var policy = Policy.Handle() .WaitAndRetry(3, n => TimeSpan.FromSeconds(0.5 * Math.Exp(n))); - if (deserialized != null) - policy.Execute(() => handler.Handle(deserialized, token)); + if (deserialized != null) policy.Execute(() => handler.Handle(deserialized, token)); } catch (Exception ex) { diff --git a/Botticelli.Bus.Rabbit/Botticelli.Bus.Rabbit.csproj b/Botticelli.Bus.Rabbit/Botticelli.Bus.Rabbit.csproj index de8ad7b1..e04851e8 100644 --- a/Botticelli.Bus.Rabbit/Botticelli.Bus.Rabbit.csproj +++ b/Botticelli.Bus.Rabbit/Botticelli.Bus.Rabbit.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli.Bus.Rabbit BotticelliBots new_logo_compact.png diff --git a/Botticelli.Bus.Rabbit/Client/RabbitClient.cs b/Botticelli.Bus.Rabbit/Client/RabbitClient.cs index 94afdff2..9323c0f6 100644 --- a/Botticelli.Bus.Rabbit/Client/RabbitClient.cs +++ b/Botticelli.Bus.Rabbit/Client/RabbitClient.cs @@ -45,13 +45,11 @@ public async IAsyncEnumerable SendAndGetResponseSeries(Send Send(request, channel, GetRequestQueueName()); - if (request.Message.Uid == null) - yield break; - - if (!_responses.TryGetValue(request.Message.Uid, out var prevValue)) - yield break; + if (request.Message.Uid == null) yield break; - while (prevValue is { IsPartial: true, IsFinal: true }) + if (!_responses.TryGetValue(request.Message.Uid, out var prevValue)) yield break; + + while (prevValue is {IsPartial: true, IsFinal: true}) if (_responses.TryGetValue(request.Message.Uid, out var value)) { if (value.IsFinal) yield return value; @@ -65,7 +63,7 @@ public async IAsyncEnumerable SendAndGetResponseSeries(Send } public async Task SendAndGetResponse(SendMessageRequest request, - CancellationToken token) + CancellationToken token) { try { @@ -138,8 +136,8 @@ private void Init() _ = _settings .QueueSettings .TryCreate ? - channel.QueueDeclare(queue, _settings.QueueSettings.Durable, false) : - channel.QueueDeclarePassive(queue); + channel.QueueDeclare(queue, _settings.QueueSettings.Durable, false) : + channel.QueueDeclarePassive(queue); channel.BasicConsume(queue, true, _consumer); @@ -157,8 +155,7 @@ private void Init() response.Message.NotNull(); response.Message.Uid.NotNull(); - if (response.Message.Uid != null) - _responses.Add(response.Message.Uid, response); + if (response.Message.Uid != null) _responses.Add(response.Message.Uid, response); } catch (Exception ex) { diff --git a/Botticelli.Bus.Rabbit/Client/RabbitEventBusClient.cs b/Botticelli.Bus.Rabbit/Client/RabbitEventBusClient.cs index 9f6bddf5..c47067a1 100644 --- a/Botticelli.Bus.Rabbit/Client/RabbitEventBusClient.cs +++ b/Botticelli.Bus.Rabbit/Client/RabbitEventBusClient.cs @@ -20,7 +20,7 @@ public class RabbitEventBusClient : BasicFunctions, IEventBusClient public RabbitEventBusClient(IConnectionFactory rabbitConnectionFactory, RabbitBusSettings settings, - ILogger> logger, + ILogger> logger, EventingBasicConsumer consumer) { _rabbitConnectionFactory = rabbitConnectionFactory; @@ -66,7 +66,7 @@ private void Init() var exchange = _settings.Exchange; - if (_settings.QueueSettings is { TryCreate: true }) + if (_settings.QueueSettings is {TryCreate: true}) { channel.ExchangeDeclare(exchange, _settings.ExchangeType); channel.QueueDeclare(queue, _settings.QueueSettings.Durable, false); diff --git a/Botticelli.Bus.Rabbit/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Bus.Rabbit/Extensions/ServiceCollectionExtensions.cs index 930c7978..6a4d860e 100644 --- a/Botticelli.Bus.Rabbit/Extensions/ServiceCollectionExtensions.cs +++ b/Botticelli.Bus.Rabbit/Extensions/ServiceCollectionExtensions.cs @@ -34,7 +34,7 @@ private static IServiceCollection AddConnectionFactory(this IServiceCollection s { settings.NotNull(); settings.Uri.NotNull(); - + if (!services.Any(s => s.ServiceType.IsAssignableFrom(typeof(IConnectionFactory)))) services.AddSingleton(s => new ConnectionFactory { diff --git a/Botticelli.Bus/Agent/PassAgent.cs b/Botticelli.Bus/Agent/PassAgent.cs index d063acbc..646da275 100644 --- a/Botticelli.Bus/Agent/PassAgent.cs +++ b/Botticelli.Bus/Agent/PassAgent.cs @@ -33,10 +33,11 @@ public Task Subscribe(CancellationToken token) /// /// public Task SendResponseAsync(SendMessageResponse response, - CancellationToken token, - int timeoutMs = 10000) + CancellationToken token, + int timeoutMs = 10000) { NoneBus.SendMessageResponses.Enqueue(response); + return Task.CompletedTask; } diff --git a/Botticelli.Bus/Botticelli.Bus.None.csproj b/Botticelli.Bus/Botticelli.Bus.None.csproj index 6b7117cc..aea412cb 100644 --- a/Botticelli.Bus/Botticelli.Bus.None.csproj +++ b/Botticelli.Bus/Botticelli.Bus.None.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli BotticelliBots https://botticellibots.com diff --git a/Botticelli.Bus/Client/PassClient.cs b/Botticelli.Bus/Client/PassClient.cs index 7bc40632..8b95e456 100644 --- a/Botticelli.Bus/Client/PassClient.cs +++ b/Botticelli.Bus/Client/PassClient.cs @@ -12,7 +12,7 @@ public class PassClient : IBusClient private static TimeSpan Timeout => TimeSpan.FromMinutes(5); public Task SendAndGetResponse(SendMessageRequest request, - CancellationToken token) + CancellationToken token) { NoneBus.SendMessageRequests.Enqueue(request); @@ -24,7 +24,8 @@ public Task SendAndGetResponse(SendMessageRequest request, while (period < Timeout.TotalMilliseconds) { if (NoneBus.SendMessageResponses.TryDequeue(out var response)) - if (response.Uid == request.Uid) return response; + if (response.Uid == request.Uid) + return response; Task.Delay(pause, token).Wait(token); period += pause; @@ -71,6 +72,7 @@ public async IAsyncEnumerable SendAndGetResponseSeries(Send public Task SendResponse(SendMessageResponse response, CancellationToken tokens) { NoneBus.SendMessageResponses.Enqueue(response); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Botticelli.Chained.Context.InMemory/Botticelli.Chained.Context.InMemory.csproj b/Botticelli.Chained.Context.InMemory/Botticelli.Chained.Context.InMemory.csproj new file mode 100644 index 00000000..5b455fb5 --- /dev/null +++ b/Botticelli.Chained.Context.InMemory/Botticelli.Chained.Context.InMemory.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Botticelli.Chained.Context.InMemory/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Chained.Context.InMemory/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..2ee663b9 --- /dev/null +++ b/Botticelli.Chained.Context.InMemory/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Botticelli.Chained.Context.InMemory.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddChainedInMemoryStorage(this IServiceCollection services, Action> builderFunc) + where TKey : notnull + { + InMemoryContextStorageBuilder builder = new(); + builderFunc(builder); + + services.AddSingleton, InMemoryStorage>(_ => builder.Build()); + + return services; + } +} \ No newline at end of file diff --git a/Botticelli.Chained.Context.InMemory/InMemoryContextStorageBuilder.cs b/Botticelli.Chained.Context.InMemory/InMemoryContextStorageBuilder.cs new file mode 100644 index 00000000..b5022953 --- /dev/null +++ b/Botticelli.Chained.Context.InMemory/InMemoryContextStorageBuilder.cs @@ -0,0 +1,19 @@ +using Botticelli.Chained.Context.Settings; + +namespace Botticelli.Chained.Context.InMemory; + +public class InMemoryContextStorageBuilder : IContextStorageBuilder, TKey, TValue> + where TKey : notnull +{ + private int? _capacity; + + public InMemoryStorage Build() => + _capacity == null ? new InMemoryStorage() : new InMemoryStorage(_capacity.Value); + + public InMemoryContextStorageBuilder SetInitialCapacity(int capacity) + { + _capacity = capacity; + + return this; + } +} \ No newline at end of file diff --git a/Botticelli.Chained.Context.InMemory/InMemoryStorage.cs b/Botticelli.Chained.Context.InMemory/InMemoryStorage.cs new file mode 100644 index 00000000..efc7d68c --- /dev/null +++ b/Botticelli.Chained.Context.InMemory/InMemoryStorage.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Botticelli.Chained.Context.InMemory; + +/// +/// In-memory storage +/// +/// +/// +public class InMemoryStorage : IStorage + where TKey : notnull +{ + private readonly Dictionary _dictionary; + + public InMemoryStorage() + { + _dictionary = new Dictionary(); + } + + public InMemoryStorage(int capacity) + { + _dictionary = new Dictionary(capacity); + } + + /// + public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key); + + /// + public void Add(TKey key, TValue? value) => _dictionary.Add(key, value); + + /// + public bool Remove(TKey key) => _dictionary.Remove(key); + + /// + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => + _dictionary.TryGetValue(key, out value); + + /// + public TValue? this[TKey key] + { + get => _dictionary[key]; + set => _dictionary[key] = value; + } +} \ No newline at end of file diff --git a/Botticelli.Chained.Context.Redis/Botticelli.Chained.Context.Redis.csproj b/Botticelli.Chained.Context.Redis/Botticelli.Chained.Context.Redis.csproj new file mode 100644 index 00000000..0673dc27 --- /dev/null +++ b/Botticelli.Chained.Context.Redis/Botticelli.Chained.Context.Redis.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Botticelli.Chained.Context.Redis/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Chained.Context.Redis/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..734f9dbd --- /dev/null +++ b/Botticelli.Chained.Context.Redis/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,31 @@ +using System.Configuration; +using Botticelli.Chained.Context.Redis.Settings; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Botticelli.Chained.Context.Redis.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddChainedRedisStorage(this IServiceCollection services, + Action> builderFunc) + where TKey : notnull + where TValue : class + { + RedisContextStorageBuilder builder = new(); + builderFunc(builder); + + services.AddSingleton, RedisStorage>(_ => builder.Build()); + + return services; + } + + public static IServiceCollection AddChainedRedisStorage(this IServiceCollection services, IConfiguration configuration) + where TKey : notnull + where TValue : class + { + var settings = configuration.GetRequiredSection(nameof(RedisStorageSettings)).Get(); + + return AddChainedRedisStorage(services, opt => opt.AddConnectionString(settings.ConnectionString)); + } +} \ No newline at end of file diff --git a/Botticelli.Chained.Context.Redis/RedisContextStorageBuilder.cs b/Botticelli.Chained.Context.Redis/RedisContextStorageBuilder.cs new file mode 100644 index 00000000..1f8f9cf1 --- /dev/null +++ b/Botticelli.Chained.Context.Redis/RedisContextStorageBuilder.cs @@ -0,0 +1,26 @@ +using System.Configuration; +using Botticelli.Chained.Context.Settings; + +namespace Botticelli.Chained.Context.Redis; + +public class RedisContextStorageBuilder : IContextStorageBuilder, TKey, TValue> + where TKey : notnull + where TValue : class +{ + private string? _connectionString; + + public RedisContextStorageBuilder AddConnectionString(string connectionString) + { + _connectionString = connectionString; + + return this; + } + + public RedisStorage Build() + { + if (_connectionString != null) + return new(_connectionString); + + throw new ConfigurationErrorsException("No connection string for redis was given!"); + } +} \ No newline at end of file diff --git a/Botticelli.Chained.Context.Redis/RedisStorage.cs b/Botticelli.Chained.Context.Redis/RedisStorage.cs new file mode 100644 index 00000000..26713a54 --- /dev/null +++ b/Botticelli.Chained.Context.Redis/RedisStorage.cs @@ -0,0 +1,81 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using StackExchange.Redis; + +namespace Botticelli.Chained.Context.Redis; + +/// +/// RedisStorage storage +/// +/// +/// +public class RedisStorage : IStorage + where TKey : notnull where TValue : class +{ + private readonly IDatabase _database; + + public RedisStorage(string connectionString) + { + var redis = ConnectionMultiplexer.Connect(connectionString); + _database = redis.GetDatabase(); + } + + public RedisStorage(IDatabase database) + { + _database = database; + } + + public bool ContainsKey(TKey key) => _database.KeyExists(key.ToString()); + + public void Add(TKey key, TValue? value) + { + _database.StringSet(key.ToString(), + value is not string ? JsonSerializer.Serialize(value?.ToString()) : value.ToString()); + } + + public bool Remove(TKey key) => _database.KeyDelete(key.ToString()); + + public bool TryGetValue(TKey key, out TValue? value) + { + value = default!; + + if (!ContainsKey(key)) + return false; + + InnerGet(key, ref value); + + return true; + } + + private void InnerGet(TKey key, [DisallowNull] ref TValue? value) + { + if (value is string) + { + value = _database.StringGet(key.ToString()) as TValue; + } + else + { + var text = _database.StringGet(key.ToString()); + + if (text is { HasValue: true }) + value = JsonSerializer.Deserialize(text!); + } + } + + public TValue? this[TKey key] + { + get + { + if (!ContainsKey(key)) + return null; + + TValue? value = default!; + + InnerGet(key, ref value); + + return value; + } + set => Add(key, value); + } +} \ No newline at end of file diff --git a/Botticelli.Chained.Context.Redis/Settings/RedisStorageSettings.cs b/Botticelli.Chained.Context.Redis/Settings/RedisStorageSettings.cs new file mode 100644 index 00000000..ce86a81f --- /dev/null +++ b/Botticelli.Chained.Context.Redis/Settings/RedisStorageSettings.cs @@ -0,0 +1,7 @@ +namespace Botticelli.Chained.Context.Redis.Settings; + +public class RedisStorageSettings +{ + + public string? ConnectionString { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Chained.Context/Botticelli.Chained.Context.csproj b/Botticelli.Chained.Context/Botticelli.Chained.Context.csproj new file mode 100644 index 00000000..b6e3fde8 --- /dev/null +++ b/Botticelli.Chained.Context/Botticelli.Chained.Context.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Botticelli.Framework.Monads/Commands/Context/CommandContext.cs b/Botticelli.Chained.Context/CommandContext.cs similarity index 73% rename from Botticelli.Framework.Monads/Commands/Context/CommandContext.cs rename to Botticelli.Chained.Context/CommandContext.cs index d65d68b3..63e4b117 100644 --- a/Botticelli.Framework.Monads/Commands/Context/CommandContext.cs +++ b/Botticelli.Chained.Context/CommandContext.cs @@ -1,18 +1,23 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; -namespace Botticelli.Framework.Monads.Commands.Context; +namespace Botticelli.Chained.Context; /// /// Command context for transmitting data over command context chain /// public class CommandContext : ICommandContext { - private readonly Dictionary _parameters = new(); + private readonly IStorage _storage; + + public CommandContext(IStorage storage) + { + _storage = storage; + } public T? Get(string name) { - if (!_parameters.TryGetValue(name, out var parameter)) return default; + if (!_storage.TryGetValue(name, out var parameter)) return default; var type = typeof(T); @@ -34,7 +39,7 @@ public class CommandContext : ICommandContext public string Get(string name) { - return _parameters[name]; + return _storage[name]; } public T Set(string name, T value) @@ -42,16 +47,16 @@ public T Set(string name, T value) if (value is null) throw new ArgumentNullException(nameof(value)); if (name == Names.Args) // args are always string - _parameters[name] = Stringify(value); + _storage[name] = Stringify(value); else - _parameters[name] = JsonSerializer.Serialize(value); + _storage[name] = JsonSerializer.Serialize(value); return value; } public string Set(string name, string value) { - _parameters[name] = Stringify(value); + _storage[name] = Stringify(value); return value; } @@ -59,11 +64,11 @@ public string Set(string name, string value) public T Transform(string name, Func func) { - if (!_parameters.TryGetValue(name, out var parameter)) throw new KeyNotFoundException(name); + if (!_storage.TryGetValue(name, out var parameter)) throw new KeyNotFoundException(name); var deserialized = JsonSerializer.Deserialize(parameter); - _parameters[name] = JsonSerializer.Serialize(func(deserialized)); + _storage[name] = JsonSerializer.Serialize(func(deserialized)); return deserialized; } diff --git a/Botticelli.Chained.Context/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Chained.Context/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..4ce26845 --- /dev/null +++ b/Botticelli.Chained.Context/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Botticelli.Chained.Context.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddChainedStorage(this IServiceCollection services) + where TStorage : class, IStorage + where TKey : notnull + { + services.AddSingleton, TStorage>(); + + return services; + } +} \ No newline at end of file diff --git a/Botticelli.Framework.Monads/Commands/Context/ICommandContext.cs b/Botticelli.Chained.Context/ICommandContext.cs similarity index 85% rename from Botticelli.Framework.Monads/Commands/Context/ICommandContext.cs rename to Botticelli.Chained.Context/ICommandContext.cs index 70d54bb3..50409f36 100644 --- a/Botticelli.Framework.Monads/Commands/Context/ICommandContext.cs +++ b/Botticelli.Chained.Context/ICommandContext.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Monads.Commands.Context; +namespace Botticelli.Chained.Context; /// /// A context for monad-based commands diff --git a/Botticelli.Chained.Context/IStorage.cs b/Botticelli.Chained.Context/IStorage.cs new file mode 100644 index 00000000..cf8fd2bb --- /dev/null +++ b/Botticelli.Chained.Context/IStorage.cs @@ -0,0 +1,56 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Botticelli.Chained.Context; + +/// +/// Represents a generic key-value storage interface with basic CRUD-like operations. +/// Supports contravariant key types for flexible usage with inheritance hierarchies. +/// +/// The type of keys (contravariant - accepts the specified type or its base types). +/// The type of values to store. +public interface IStorage + where TKey : notnull +{ + /// + /// Determines whether the storage contains the specified key. + /// + /// The key to locate. + /// True if the key exists; otherwise, false. + bool ContainsKey(TKey key); + + /// + /// Adds a new key-value pair to the storage. + /// + /// The key of the element to add. + /// The value of the element to add. + /// Thrown if the key already exists. + void Add(TKey key, TValue? value); + + /// + /// Removes the key-value pair with the specified key from the storage. + /// + /// The key of the element to remove. + /// True if the element was successfully removed; otherwise, false. + bool Remove(TKey key); + + /// + /// Attempts to retrieve the value associated with the specified key. + /// + /// The key of the value to get. + /// + /// When this method returns, contains the value associated with the specified key, + /// if the key is found; otherwise, the default value for the type. + /// This parameter is marked as maybe null when the method returns false. + /// + /// True if the key exists; otherwise, false. + bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue? value); + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. + /// Thrown when getting a key that doesn't exist. + /// Thrown when setting a key that doesn't exist (if the storage requires explicit Add). + TValue? this[TKey key] { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Framework.Monads/Commands/Names.cs b/Botticelli.Chained.Context/Names.cs similarity index 77% rename from Botticelli.Framework.Monads/Commands/Names.cs rename to Botticelli.Chained.Context/Names.cs index 3b86b585..86037287 100644 --- a/Botticelli.Framework.Monads/Commands/Names.cs +++ b/Botticelli.Chained.Context/Names.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Monads.Commands; +namespace Botticelli.Chained.Context; public static class Names { diff --git a/Botticelli.Chained.Context/Settings/IContextStorageBuilder.cs b/Botticelli.Chained.Context/Settings/IContextStorageBuilder.cs new file mode 100644 index 00000000..e91bd262 --- /dev/null +++ b/Botticelli.Chained.Context/Settings/IContextStorageBuilder.cs @@ -0,0 +1,10 @@ +namespace Botticelli.Chained.Context.Settings; + +public interface IContextStorageBuilder +where TStorage : IStorage +where TKey : notnull +{ + private TStorage Storage => throw new NotImplementedException(); + + public TStorage Build(); +} \ No newline at end of file diff --git a/Botticelli.Framework.Monads/Botticelli.Framework.Monads.csproj b/Botticelli.Chained.Monads/Botticelli.Chained.Monads.csproj similarity index 91% rename from Botticelli.Framework.Monads/Botticelli.Framework.Monads.csproj rename to Botticelli.Chained.Monads/Botticelli.Chained.Monads.csproj index 2e7a8f09..b88aa27d 100644 --- a/Botticelli.Framework.Monads/Botticelli.Framework.Monads.csproj +++ b/Botticelli.Chained.Monads/Botticelli.Chained.Monads.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli.Framework.Monads BotticelliBots new_logo_compact.png @@ -44,7 +44,8 @@ - + + \ No newline at end of file diff --git a/Botticelli.Framework.Monads/Commands/Context/IChainCommand.cs b/Botticelli.Chained.Monads/Commands/IChainCommand.cs similarity index 62% rename from Botticelli.Framework.Monads/Commands/Context/IChainCommand.cs rename to Botticelli.Chained.Monads/Commands/IChainCommand.cs index d63f0e14..85b2f5d7 100644 --- a/Botticelli.Framework.Monads/Commands/Context/IChainCommand.cs +++ b/Botticelli.Chained.Monads/Commands/IChainCommand.cs @@ -1,6 +1,7 @@ +using Botticelli.Chained.Context; using Botticelli.Framework.Commands; -namespace Botticelli.Framework.Monads.Commands.Context; +namespace Botticelli.Chained.Monads.Commands; public interface IChainCommand : ICommand { diff --git a/Botticelli.Framework.Monads/Commands/Processors/ChainBuilder.cs b/Botticelli.Chained.Monads/Commands/Processors/ChainBuilder.cs similarity index 94% rename from Botticelli.Framework.Monads/Commands/Processors/ChainBuilder.cs rename to Botticelli.Chained.Monads/Commands/Processors/ChainBuilder.cs index a43676db..6461a181 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/ChainBuilder.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/ChainBuilder.cs @@ -1,9 +1,8 @@ -using Botticelli.Framework.Monads.Commands.Context; using Botticelli.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; public class ChainBuilder(IServiceCollection services) where TCommand : IChainCommand diff --git a/Botticelli.Framework.Monads/Commands/Processors/ChainProcessor.cs b/Botticelli.Chained.Monads/Commands/Processors/ChainProcessor.cs similarity index 91% rename from Botticelli.Framework.Monads/Commands/Processors/ChainProcessor.cs rename to Botticelli.Chained.Monads/Commands/Processors/ChainProcessor.cs index d8b931a1..b4aabce0 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/ChainProcessor.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/ChainProcessor.cs @@ -1,11 +1,10 @@ -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Result; +using Botticelli.Chained.Monads.Commands.Result; using Botticelli.Interfaces; using Botticelli.Shared.ValueObjects; using LanguageExt; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; /// /// Chain processor diff --git a/Botticelli.Framework.Monads/Commands/Processors/ChainRunProcessor.cs b/Botticelli.Chained.Monads/Commands/Processors/ChainRunProcessor.cs similarity index 66% rename from Botticelli.Framework.Monads/Commands/Processors/ChainRunProcessor.cs rename to Botticelli.Chained.Monads/Commands/Processors/ChainRunProcessor.cs index 0b1f815d..c3866bf4 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/ChainRunProcessor.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/ChainRunProcessor.cs @@ -1,31 +1,35 @@ using Botticelli.Client.Analytics; +using Botticelli.Chained.Context; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Utils; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Monads.Commands.Context; using Botticelli.Shared.ValueObjects; using FluentValidation; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; public class ChainRunProcessor( ILogger> logger, ICommandValidator validator, MetricsProcessor metricsProcessor, ChainRunner chainRunner, - IValidator messageValidator) + IValidator messageValidator, + IServiceProvider serviceProvider) : CommandProcessor(logger, validator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) where TCommand : class, IChainCommand, new() { protected override async Task InnerProcess(Message message, CancellationToken token) { + var storage = serviceProvider.GetRequiredService>(); + var command = new TCommand { - Context = new CommandContext() + Context = new CommandContext(storage) }; command.Context.Set(Names.Message, message); diff --git a/Botticelli.Framework.Monads/Commands/Processors/ChainRunner.cs b/Botticelli.Chained.Monads/Commands/Processors/ChainRunner.cs similarity index 90% rename from Botticelli.Framework.Monads/Commands/Processors/ChainRunner.cs rename to Botticelli.Chained.Monads/Commands/Processors/ChainRunner.cs index c97ad6d7..c35c8d22 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/ChainRunner.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/ChainRunner.cs @@ -1,9 +1,8 @@ -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Result; +using Botticelli.Chained.Monads.Commands.Result; using LanguageExt; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; public class ChainRunner(List> chain, ILogger> logger) where TCommand : IChainCommand diff --git a/Botticelli.Framework.Monads/Commands/Processors/IChainProcessor.cs b/Botticelli.Chained.Monads/Commands/Processors/IChainProcessor.cs similarity index 74% rename from Botticelli.Framework.Monads/Commands/Processors/IChainProcessor.cs rename to Botticelli.Chained.Monads/Commands/Processors/IChainProcessor.cs index 54051535..c17070fe 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/IChainProcessor.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/IChainProcessor.cs @@ -1,9 +1,8 @@ -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Result; +using Botticelli.Chained.Monads.Commands.Result; using Botticelli.Interfaces; using LanguageExt; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; /// /// Chain processor diff --git a/Botticelli.Framework.Monads/Commands/Processors/InputCommandProcessor.cs b/Botticelli.Chained.Monads/Commands/Processors/InputCommandProcessor.cs similarity index 76% rename from Botticelli.Framework.Monads/Commands/Processors/InputCommandProcessor.cs rename to Botticelli.Chained.Monads/Commands/Processors/InputCommandProcessor.cs index d48d09d0..d1650641 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/InputCommandProcessor.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/InputCommandProcessor.cs @@ -1,8 +1,7 @@ -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Result; +using Botticelli.Chained.Monads.Commands.Result; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; public class InputCommandProcessor(ILogger> logger) : ChainProcessor(logger) diff --git a/Botticelli.Framework.Monads/Commands/Processors/OutputCommandProcessor.cs b/Botticelli.Chained.Monads/Commands/Processors/OutputCommandProcessor.cs similarity index 87% rename from Botticelli.Framework.Monads/Commands/Processors/OutputCommandProcessor.cs rename to Botticelli.Chained.Monads/Commands/Processors/OutputCommandProcessor.cs index adff9a4e..db84e5d8 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/OutputCommandProcessor.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/OutputCommandProcessor.cs @@ -1,12 +1,11 @@ -using Botticelli.Framework.Controls.Parsers; -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Result; +using Botticelli.Chained.Monads.Commands.Result; +using Botticelli.Controls.Parsers; using Botticelli.Framework.SendOptions; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; public class OutputCommandProcessor : ChainProcessor where TReplyMarkup : class diff --git a/Botticelli.Framework.Monads/Commands/Processors/TransformArgumentsProcessor.cs b/Botticelli.Chained.Monads/Commands/Processors/TransformArgumentsProcessor.cs similarity index 94% rename from Botticelli.Framework.Monads/Commands/Processors/TransformArgumentsProcessor.cs rename to Botticelli.Chained.Monads/Commands/Processors/TransformArgumentsProcessor.cs index 2825a284..3453c908 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/TransformArgumentsProcessor.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/TransformArgumentsProcessor.cs @@ -1,9 +1,9 @@ -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Result; +using Botticelli.Chained.Context; +using Botticelli.Chained.Monads.Commands.Result; using LanguageExt; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; /// /// Func transform for command arguments processor diff --git a/Botticelli.Framework.Monads/Commands/Processors/TransformProcessor.cs b/Botticelli.Chained.Monads/Commands/Processors/TransformProcessor.cs similarity index 90% rename from Botticelli.Framework.Monads/Commands/Processors/TransformProcessor.cs rename to Botticelli.Chained.Monads/Commands/Processors/TransformProcessor.cs index bbbfa87d..cc4a909a 100644 --- a/Botticelli.Framework.Monads/Commands/Processors/TransformProcessor.cs +++ b/Botticelli.Chained.Monads/Commands/Processors/TransformProcessor.cs @@ -1,9 +1,8 @@ -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Result; +using Botticelli.Chained.Monads.Commands.Result; using LanguageExt; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Monads.Commands.Processors; +namespace Botticelli.Chained.Monads.Commands.Processors; /// /// Func transform processor diff --git a/Botticelli.Framework.Monads/Commands/Result/BasicResult.cs b/Botticelli.Chained.Monads/Commands/Result/BasicResult.cs similarity index 85% rename from Botticelli.Framework.Monads/Commands/Result/BasicResult.cs rename to Botticelli.Chained.Monads/Commands/Result/BasicResult.cs index 743acb40..3fc60fe1 100644 --- a/Botticelli.Framework.Monads/Commands/Result/BasicResult.cs +++ b/Botticelli.Chained.Monads/Commands/Result/BasicResult.cs @@ -1,6 +1,6 @@ using Botticelli.Framework.Commands; -namespace Botticelli.Framework.Monads.Commands.Result; +namespace Botticelli.Chained.Monads.Commands.Result; public class BasicResult : IResult where TCommand : ICommand diff --git a/Botticelli.Framework.Monads/Commands/Result/FailResult.cs b/Botticelli.Chained.Monads/Commands/Result/FailResult.cs similarity index 92% rename from Botticelli.Framework.Monads/Commands/Result/FailResult.cs rename to Botticelli.Chained.Monads/Commands/Result/FailResult.cs index 80db04c9..1f8b4f5f 100644 --- a/Botticelli.Framework.Monads/Commands/Result/FailResult.cs +++ b/Botticelli.Chained.Monads/Commands/Result/FailResult.cs @@ -1,7 +1,7 @@ using Botticelli.Framework.Commands; using Botticelli.Shared.ValueObjects; -namespace Botticelli.Framework.Monads.Commands.Result; +namespace Botticelli.Chained.Monads.Commands.Result; /// /// Fail result diff --git a/Botticelli.Framework.Monads/Commands/Result/IResult.cs b/Botticelli.Chained.Monads/Commands/Result/IResult.cs similarity index 82% rename from Botticelli.Framework.Monads/Commands/Result/IResult.cs rename to Botticelli.Chained.Monads/Commands/Result/IResult.cs index 52c87116..2f6ffdcd 100644 --- a/Botticelli.Framework.Monads/Commands/Result/IResult.cs +++ b/Botticelli.Chained.Monads/Commands/Result/IResult.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using Botticelli.Framework.Commands; -namespace Botticelli.Framework.Monads.Commands.Result; +namespace Botticelli.Chained.Monads.Commands.Result; public interface IResult where TCommand : ICommand diff --git a/Botticelli.Framework.Monads/Commands/Result/SuccessResult.cs b/Botticelli.Chained.Monads/Commands/Result/SuccessResult.cs similarity index 90% rename from Botticelli.Framework.Monads/Commands/Result/SuccessResult.cs rename to Botticelli.Chained.Monads/Commands/Result/SuccessResult.cs index 03a5eda6..13b9968a 100644 --- a/Botticelli.Framework.Monads/Commands/Result/SuccessResult.cs +++ b/Botticelli.Chained.Monads/Commands/Result/SuccessResult.cs @@ -1,7 +1,7 @@ using Botticelli.Framework.Commands; using Botticelli.Shared.ValueObjects; -namespace Botticelli.Framework.Monads.Commands.Result; +namespace Botticelli.Chained.Monads.Commands.Result; /// /// Success result diff --git a/Botticelli.Framework.Monads/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Chained.Monads/Extensions/ServiceCollectionExtensions.cs similarity index 84% rename from Botticelli.Framework.Monads/Extensions/ServiceCollectionExtensions.cs rename to Botticelli.Chained.Monads/Extensions/ServiceCollectionExtensions.cs index d7f3291a..f9abac5f 100644 --- a/Botticelli.Framework.Monads/Extensions/ServiceCollectionExtensions.cs +++ b/Botticelli.Chained.Monads/Extensions/ServiceCollectionExtensions.cs @@ -1,11 +1,11 @@ +using Botticelli.Chained.Monads.Commands; +using Botticelli.Chained.Monads.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Extensions; -using Botticelli.Framework.Monads.Commands.Context; -using Botticelli.Framework.Monads.Commands.Processors; using Microsoft.Extensions.DependencyInjection; -namespace Botticelli.Framework.Monads.Extensions; +namespace Botticelli.Chained.Monads.Extensions; public static class ServiceCollectionExtensions { @@ -20,7 +20,7 @@ public static CommandAddServices AddMonadsChain( var runner = chainBuilder.Build(); - services.AddScoped(_ => runner); + services.AddSingleton(_ => runner); return commandAddServices.AddProcessor>() .AddValidator(); @@ -38,8 +38,8 @@ public static CommandAddServices AddMonadsChain runner) - .AddScoped, TLayoutSupplier>(); + services.AddSingleton(_ => runner) + .AddSingleton, TLayoutSupplier>(); return commandAddServices.AddProcessor>() .AddValidator(); diff --git a/Botticelli.Client.Analytics/Botticelli.Client.Analytics.csproj b/Botticelli.Client.Analytics/Botticelli.Client.Analytics.csproj index 5c9a796c..0b64dc16 100644 --- a/Botticelli.Client.Analytics/Botticelli.Client.Analytics.csproj +++ b/Botticelli.Client.Analytics/Botticelli.Client.Analytics.csproj @@ -5,7 +5,7 @@ enable enable true - 0.7.0 + 0.8.0 https://botticellibots.com new_logo_compact.png https://github.com/devgopher/botticelli diff --git a/Botticelli.Client.Analytics/Handlers/MetricsHandler.cs b/Botticelli.Client.Analytics/Handlers/MetricsHandler.cs index 4da094fc..1651fbcd 100644 --- a/Botticelli.Client.Analytics/Handlers/MetricsHandler.cs +++ b/Botticelli.Client.Analytics/Handlers/MetricsHandler.cs @@ -23,7 +23,7 @@ public virtual async Task Handle(IMetricRequest request, CancellationToken cance { try { - _logger.LogTrace($"Metric {request.GetType().Name} handling..."); + _logger.LogTrace("Metric {Name} handling...", request.GetType().Name); var metric = Convert(request, _context.BotId); diff --git a/Botticelli.Framework.Controls.Layouts/Botticelli.Framework.Controls.Layouts.csproj b/Botticelli.Controls.Layouts/Botticelli.Controls.Layouts.csproj similarity index 86% rename from Botticelli.Framework.Controls.Layouts/Botticelli.Framework.Controls.Layouts.csproj rename to Botticelli.Controls.Layouts/Botticelli.Controls.Layouts.csproj index b8b967b8..d6e3ae31 100644 --- a/Botticelli.Framework.Controls.Layouts/Botticelli.Framework.Controls.Layouts.csproj +++ b/Botticelli.Controls.Layouts/Botticelli.Controls.Layouts.csproj @@ -7,17 +7,15 @@ - - - + diff --git a/Botticelli.Framework.Controls.Layouts/CommandProcessors/InlineCalendar/ICCommandProcessor.cs b/Botticelli.Controls.Layouts/CommandProcessors/InlineCalendar/ICCommandProcessor.cs similarity index 67% rename from Botticelli.Framework.Controls.Layouts/CommandProcessors/InlineCalendar/ICCommandProcessor.cs rename to Botticelli.Controls.Layouts/CommandProcessors/InlineCalendar/ICCommandProcessor.cs index dcbf59d8..0a617415 100644 --- a/Botticelli.Framework.Controls.Layouts/CommandProcessors/InlineCalendar/ICCommandProcessor.cs +++ b/Botticelli.Controls.Layouts/CommandProcessors/InlineCalendar/ICCommandProcessor.cs @@ -3,16 +3,15 @@ using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Utils; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; -using Botticelli.Framework.Controls.Layouts.Inlines; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Layouts.Commands.InlineCalendar; +using Botticelli.Controls.Layouts.Inlines; +using Botticelli.Controls.Parsers; using Botticelli.Framework.SendOptions; -using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; using FluentValidation; using Microsoft.Extensions.Logging; -namespace Botticelli.Framework.Controls.Layouts.CommandProcessors.InlineCalendar; +namespace Botticelli.Controls.Layouts.CommandProcessors.InlineCalendar; /// /// Calendar command processor @@ -31,8 +30,8 @@ public ICCommandProcessor(ILogger> lo IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { _layoutSupplier = layoutSupplier; } @@ -57,18 +56,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to var responseMarkup = _layoutSupplier.GetMarkup(calendar); var options = SendOptionsBuilder.CreateBuilder(responseMarkup); - await Bot.UpdateMessageAsync(new SendMessageRequest - { - ExpectPartialResponse = false, - Message = new Message - { - Body = message.CallbackData, - Uid = message.Uid, - ChatIds = message.ChatIds, - ChatIdInnerIdLinks = message.ChatIdInnerIdLinks - } - }, - options, - token); + await UpdateMessage(message, options, token); } } \ No newline at end of file diff --git a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/BaseCalendarCommand.cs b/Botticelli.Controls.Layouts/Commands/InlineCalendar/BaseCalendarCommand.cs similarity index 69% rename from Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/BaseCalendarCommand.cs rename to Botticelli.Controls.Layouts/Commands/InlineCalendar/BaseCalendarCommand.cs index bb051769..7bc019ce 100644 --- a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/BaseCalendarCommand.cs +++ b/Botticelli.Controls.Layouts/Commands/InlineCalendar/BaseCalendarCommand.cs @@ -1,6 +1,6 @@ using Botticelli.Framework.Commands; -namespace Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; +namespace Botticelli.Controls.Layouts.Commands.InlineCalendar; public abstract class BaseCalendarCommand : ICommand { diff --git a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/DateChosenCommand.cs b/Botticelli.Controls.Layouts/Commands/InlineCalendar/DateChosenCommand.cs similarity index 69% rename from Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/DateChosenCommand.cs rename to Botticelli.Controls.Layouts/Commands/InlineCalendar/DateChosenCommand.cs index e3de8b04..df3c7f2f 100644 --- a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/DateChosenCommand.cs +++ b/Botticelli.Controls.Layouts/Commands/InlineCalendar/DateChosenCommand.cs @@ -1,6 +1,6 @@ using Botticelli.Framework.Commands; -namespace Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; +namespace Botticelli.Controls.Layouts.Commands.InlineCalendar; public abstract class DateChosenCommand : ICommand { diff --git a/Botticelli.Controls.Layouts/Commands/InlineCalendar/MonthBackwardCommand.cs b/Botticelli.Controls.Layouts/Commands/InlineCalendar/MonthBackwardCommand.cs new file mode 100644 index 00000000..05bfcef9 --- /dev/null +++ b/Botticelli.Controls.Layouts/Commands/InlineCalendar/MonthBackwardCommand.cs @@ -0,0 +1,5 @@ +namespace Botticelli.Controls.Layouts.Commands.InlineCalendar; + +public class MonthBackwardCommand : BaseCalendarCommand +{ +} \ No newline at end of file diff --git a/Botticelli.Controls.Layouts/Commands/InlineCalendar/MonthForwardCommand.cs b/Botticelli.Controls.Layouts/Commands/InlineCalendar/MonthForwardCommand.cs new file mode 100644 index 00000000..0681bfbf --- /dev/null +++ b/Botticelli.Controls.Layouts/Commands/InlineCalendar/MonthForwardCommand.cs @@ -0,0 +1,5 @@ +namespace Botticelli.Controls.Layouts.Commands.InlineCalendar; + +public class MonthForwardCommand : BaseCalendarCommand +{ +} \ No newline at end of file diff --git a/Botticelli.Controls.Layouts/Commands/InlineCalendar/SetDateCommand.cs b/Botticelli.Controls.Layouts/Commands/InlineCalendar/SetDateCommand.cs new file mode 100644 index 00000000..03cf7d77 --- /dev/null +++ b/Botticelli.Controls.Layouts/Commands/InlineCalendar/SetDateCommand.cs @@ -0,0 +1,5 @@ +namespace Botticelli.Controls.Layouts.Commands.InlineCalendar; + +public class SetDateCommand : BaseCalendarCommand +{ +} \ No newline at end of file diff --git a/Botticelli.Controls.Layouts/Commands/InlineCalendar/YearBackwardCommand.cs b/Botticelli.Controls.Layouts/Commands/InlineCalendar/YearBackwardCommand.cs new file mode 100644 index 00000000..4bf8bd40 --- /dev/null +++ b/Botticelli.Controls.Layouts/Commands/InlineCalendar/YearBackwardCommand.cs @@ -0,0 +1,5 @@ +namespace Botticelli.Controls.Layouts.Commands.InlineCalendar; + +public class YearBackwardCommand : BaseCalendarCommand +{ +} \ No newline at end of file diff --git a/Botticelli.Controls.Layouts/Commands/InlineCalendar/YearForwardCommand.cs b/Botticelli.Controls.Layouts/Commands/InlineCalendar/YearForwardCommand.cs new file mode 100644 index 00000000..14060177 --- /dev/null +++ b/Botticelli.Controls.Layouts/Commands/InlineCalendar/YearForwardCommand.cs @@ -0,0 +1,5 @@ +namespace Botticelli.Controls.Layouts.Commands.InlineCalendar; + +public class YearForwardCommand : BaseCalendarCommand +{ +} \ No newline at end of file diff --git a/Botticelli.Framework.Controls.Layouts/Dialogs/yes_no.json b/Botticelli.Controls.Layouts/Dialogs/yes_no.json similarity index 100% rename from Botticelli.Framework.Controls.Layouts/Dialogs/yes_no.json rename to Botticelli.Controls.Layouts/Dialogs/yes_no.json diff --git a/Botticelli.Framework.Controls.Layouts/Dialogs/yes_no_cancel.json b/Botticelli.Controls.Layouts/Dialogs/yes_no_cancel.json similarity index 100% rename from Botticelli.Framework.Controls.Layouts/Dialogs/yes_no_cancel.json rename to Botticelli.Controls.Layouts/Dialogs/yes_no_cancel.json diff --git a/Botticelli.Framework.Controls.Layouts/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Controls.Layouts/Extensions/ServiceCollectionExtensions.cs similarity index 87% rename from Botticelli.Framework.Controls.Layouts/Extensions/ServiceCollectionExtensions.cs rename to Botticelli.Controls.Layouts/Extensions/ServiceCollectionExtensions.cs index ff169843..ebfaa68e 100644 --- a/Botticelli.Framework.Controls.Layouts/Extensions/ServiceCollectionExtensions.cs +++ b/Botticelli.Controls.Layouts/Extensions/ServiceCollectionExtensions.cs @@ -1,12 +1,12 @@ using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Layouts.CommandProcessors.InlineCalendar; -using Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Layouts.CommandProcessors.InlineCalendar; +using Botticelli.Controls.Layouts.Commands.InlineCalendar; +using Botticelli.Controls.Parsers; using Microsoft.Extensions.DependencyInjection; using Telegram.Bot.Types.ReplyMarkups; -namespace Botticelli.Framework.Controls.Layouts.Extensions; +namespace Botticelli.Controls.Layouts.Extensions; public static class ServiceCollectionExtensions { diff --git a/Botticelli.Framework.Controls.Layouts/Inlines/CalendarFactory.cs b/Botticelli.Controls.Layouts/Inlines/CalendarFactory.cs similarity index 95% rename from Botticelli.Framework.Controls.Layouts/Inlines/CalendarFactory.cs rename to Botticelli.Controls.Layouts/Inlines/CalendarFactory.cs index 7374477a..21f17704 100644 --- a/Botticelli.Framework.Controls.Layouts/Inlines/CalendarFactory.cs +++ b/Botticelli.Controls.Layouts/Inlines/CalendarFactory.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using System.Globalization; -namespace Botticelli.Framework.Controls.Layouts.Inlines; +namespace Botticelli.Controls.Layouts.Inlines; public static class CalendarFactory { diff --git a/Botticelli.Framework.Controls.Layouts/Inlines/InlineButtonMenu.cs b/Botticelli.Controls.Layouts/Inlines/InlineButtonMenu.cs similarity index 95% rename from Botticelli.Framework.Controls.Layouts/Inlines/InlineButtonMenu.cs rename to Botticelli.Controls.Layouts/Inlines/InlineButtonMenu.cs index 56ed883c..b9e0b851 100644 --- a/Botticelli.Framework.Controls.Layouts/Inlines/InlineButtonMenu.cs +++ b/Botticelli.Controls.Layouts/Inlines/InlineButtonMenu.cs @@ -1,6 +1,6 @@ -using Botticelli.Framework.Controls.BasicControls; +using Botticelli.Controls.BasicControls; -namespace Botticelli.Framework.Controls.Layouts.Inlines; +namespace Botticelli.Controls.Layouts.Inlines; public class InlineButtonMenu : ILayout { diff --git a/Botticelli.Framework.Controls.Layouts/Inlines/InlineCalendar.cs b/Botticelli.Controls.Layouts/Inlines/InlineCalendar.cs similarity index 97% rename from Botticelli.Framework.Controls.Layouts/Inlines/InlineCalendar.cs rename to Botticelli.Controls.Layouts/Inlines/InlineCalendar.cs index 36558200..4880fee0 100644 --- a/Botticelli.Framework.Controls.Layouts/Inlines/InlineCalendar.cs +++ b/Botticelli.Controls.Layouts/Inlines/InlineCalendar.cs @@ -1,7 +1,7 @@ using System.Globalization; -using Botticelli.Framework.Controls.BasicControls; +using Botticelli.Controls.BasicControls; -namespace Botticelli.Framework.Controls.Layouts.Inlines; +namespace Botticelli.Controls.Layouts.Inlines; public class InlineCalendar : ILayout { diff --git a/Botticelli.Framework.Controls.Layouts/Inlines/Table.cs b/Botticelli.Controls.Layouts/Inlines/Table.cs similarity index 88% rename from Botticelli.Framework.Controls.Layouts/Inlines/Table.cs rename to Botticelli.Controls.Layouts/Inlines/Table.cs index c93dfbc9..0743aa2e 100644 --- a/Botticelli.Framework.Controls.Layouts/Inlines/Table.cs +++ b/Botticelli.Controls.Layouts/Inlines/Table.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Layouts.Inlines; +namespace Botticelli.Controls.Layouts.Inlines; public class Table(int cols) : ILayout { diff --git a/Botticelli.Framework.Controls.Layouts/Resources/Images/Common/check.xcf b/Botticelli.Controls.Layouts/Resources/Images/Common/check.xcf similarity index 100% rename from Botticelli.Framework.Controls.Layouts/Resources/Images/Common/check.xcf rename to Botticelli.Controls.Layouts/Resources/Images/Common/check.xcf diff --git a/Botticelli.Framework.Controls.Layouts/Resources/Images/Common/check_black.png b/Botticelli.Controls.Layouts/Resources/Images/Common/check_black.png similarity index 100% rename from Botticelli.Framework.Controls.Layouts/Resources/Images/Common/check_black.png rename to Botticelli.Controls.Layouts/Resources/Images/Common/check_black.png diff --git a/Botticelli.Framework.Controls.Layouts/Resources/Images/Common/check_green.png b/Botticelli.Controls.Layouts/Resources/Images/Common/check_green.png similarity index 100% rename from Botticelli.Framework.Controls.Layouts/Resources/Images/Common/check_green.png rename to Botticelli.Controls.Layouts/Resources/Images/Common/check_green.png diff --git a/Botticelli.Framework.Controls/BasicControls/Button.cs b/Botticelli.Controls/BasicControls/Button.cs similarity index 77% rename from Botticelli.Framework.Controls/BasicControls/Button.cs rename to Botticelli.Controls/BasicControls/Button.cs index 6b5e3028..baf80ebc 100644 --- a/Botticelli.Framework.Controls/BasicControls/Button.cs +++ b/Botticelli.Controls/BasicControls/Button.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.BasicControls; +namespace Botticelli.Controls.BasicControls; public class Button : IControl { @@ -15,8 +15,7 @@ public string? CallbackData set { Params ??= new Dictionary(); - if (value != null) - Params["CallbackData"] = value; + if (value != null) Params["CallbackData"] = value; } } } \ No newline at end of file diff --git a/Botticelli.Framework.Controls/BasicControls/IControl.cs b/Botticelli.Controls/BasicControls/IControl.cs similarity index 91% rename from Botticelli.Framework.Controls/BasicControls/IControl.cs rename to Botticelli.Controls/BasicControls/IControl.cs index 8ffcfe71..fe9286f1 100644 --- a/Botticelli.Framework.Controls/BasicControls/IControl.cs +++ b/Botticelli.Controls/BasicControls/IControl.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.BasicControls; +namespace Botticelli.Controls.BasicControls; /// /// A basic control interface diff --git a/Botticelli.Framework.Controls/BasicControls/Text.cs b/Botticelli.Controls/BasicControls/Text.cs similarity index 73% rename from Botticelli.Framework.Controls/BasicControls/Text.cs rename to Botticelli.Controls/BasicControls/Text.cs index d833636f..1e4f528f 100644 --- a/Botticelli.Framework.Controls/BasicControls/Text.cs +++ b/Botticelli.Controls/BasicControls/Text.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.BasicControls; +namespace Botticelli.Controls.BasicControls; public class Text : IControl { @@ -10,9 +10,8 @@ public string? CallbackData set { Params ??= new Dictionary(); - - if (value != null) - Params["CallbackData"] = value; + + if (value != null) Params["CallbackData"] = value; } } diff --git a/Botticelli.Controls/BasicControls/TextButton.cs b/Botticelli.Controls/BasicControls/TextButton.cs new file mode 100644 index 00000000..fa77126d --- /dev/null +++ b/Botticelli.Controls/BasicControls/TextButton.cs @@ -0,0 +1,5 @@ +namespace Botticelli.Controls.BasicControls; + +public class TextButton : Button +{ +} \ No newline at end of file diff --git a/Botticelli.Framework.Controls/Botticelli.Framework.Controls.csproj b/Botticelli.Controls/Botticelli.Controls.csproj similarity index 87% rename from Botticelli.Framework.Controls/Botticelli.Framework.Controls.csproj rename to Botticelli.Controls/Botticelli.Controls.csproj index 508345e6..86ab4d02 100644 --- a/Botticelli.Framework.Controls/Botticelli.Framework.Controls.csproj +++ b/Botticelli.Controls/Botticelli.Controls.csproj @@ -9,7 +9,7 @@ - + diff --git a/Botticelli.Framework.Controls/Exceptions/LayoutException.cs b/Botticelli.Controls/Exceptions/LayoutException.cs similarity index 80% rename from Botticelli.Framework.Controls/Exceptions/LayoutException.cs rename to Botticelli.Controls/Exceptions/LayoutException.cs index 6ec00f0c..55a8134a 100644 --- a/Botticelli.Framework.Controls/Exceptions/LayoutException.cs +++ b/Botticelli.Controls/Exceptions/LayoutException.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Exceptions; +namespace Botticelli.Controls.Exceptions; public class LayoutException : Exception { diff --git a/Botticelli.Framework.Controls/Extensions/DictionaryExtensions.cs b/Botticelli.Controls/Extensions/DictionaryExtensions.cs similarity index 88% rename from Botticelli.Framework.Controls/Extensions/DictionaryExtensions.cs rename to Botticelli.Controls/Extensions/DictionaryExtensions.cs index 73850b5c..674a8b44 100644 --- a/Botticelli.Framework.Controls/Extensions/DictionaryExtensions.cs +++ b/Botticelli.Controls/Extensions/DictionaryExtensions.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace Botticelli.Framework.Controls.Extensions; +namespace Botticelli.Controls.Extensions; public static class DictionaryExtensions { diff --git a/Botticelli.Framework.Controls/Layouts/BaseLayout.cs b/Botticelli.Controls/Layouts/BaseLayout.cs similarity index 78% rename from Botticelli.Framework.Controls/Layouts/BaseLayout.cs rename to Botticelli.Controls/Layouts/BaseLayout.cs index 66998c7a..c9cc1d38 100644 --- a/Botticelli.Framework.Controls/Layouts/BaseLayout.cs +++ b/Botticelli.Controls/Layouts/BaseLayout.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Layouts; +namespace Botticelli.Controls.Layouts; public class BaseLayout : ILayout { diff --git a/Botticelli.Framework.Controls/Layouts/CellAlign.cs b/Botticelli.Controls/Layouts/CellAlign.cs similarity index 52% rename from Botticelli.Framework.Controls/Layouts/CellAlign.cs rename to Botticelli.Controls/Layouts/CellAlign.cs index 7e2d5c73..8d067f88 100644 --- a/Botticelli.Framework.Controls/Layouts/CellAlign.cs +++ b/Botticelli.Controls/Layouts/CellAlign.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Layouts; +namespace Botticelli.Controls.Layouts; public enum CellAlign { diff --git a/Botticelli.Framework.Controls/Layouts/ILayout.cs b/Botticelli.Controls/Layouts/ILayout.cs similarity index 65% rename from Botticelli.Framework.Controls/Layouts/ILayout.cs rename to Botticelli.Controls/Layouts/ILayout.cs index 2a18d35c..14c5d2f2 100644 --- a/Botticelli.Framework.Controls/Layouts/ILayout.cs +++ b/Botticelli.Controls/Layouts/ILayout.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Layouts; +namespace Botticelli.Controls.Layouts; public interface ILayout { diff --git a/Botticelli.Framework.Controls/Layouts/Item.cs b/Botticelli.Controls/Layouts/Item.cs similarity index 51% rename from Botticelli.Framework.Controls/Layouts/Item.cs rename to Botticelli.Controls/Layouts/Item.cs index 38fa562c..38ca98b1 100644 --- a/Botticelli.Framework.Controls/Layouts/Item.cs +++ b/Botticelli.Controls/Layouts/Item.cs @@ -1,6 +1,6 @@ -using Botticelli.Framework.Controls.BasicControls; +using Botticelli.Controls.BasicControls; -namespace Botticelli.Framework.Controls.Layouts; +namespace Botticelli.Controls.Layouts; public class Item { diff --git a/Botticelli.Framework.Controls/Layouts/ItemParams.cs b/Botticelli.Controls/Layouts/ItemParams.cs similarity index 67% rename from Botticelli.Framework.Controls/Layouts/ItemParams.cs rename to Botticelli.Controls/Layouts/ItemParams.cs index 83f3e030..62e50b07 100644 --- a/Botticelli.Framework.Controls/Layouts/ItemParams.cs +++ b/Botticelli.Controls/Layouts/ItemParams.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Layouts; +namespace Botticelli.Controls.Layouts; public class ItemParams { diff --git a/Botticelli.Framework.Controls/Layouts/Row.cs b/Botticelli.Controls/Layouts/Row.cs similarity index 73% rename from Botticelli.Framework.Controls/Layouts/Row.cs rename to Botticelli.Controls/Layouts/Row.cs index d6af8a4f..b0d79295 100644 --- a/Botticelli.Framework.Controls/Layouts/Row.cs +++ b/Botticelli.Controls/Layouts/Row.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Layouts; +namespace Botticelli.Controls.Layouts; public class Row { diff --git a/Botticelli.Framework.Controls/Parsers/ILayoutLoader.cs b/Botticelli.Controls/Parsers/ILayoutLoader.cs similarity index 82% rename from Botticelli.Framework.Controls/Parsers/ILayoutLoader.cs rename to Botticelli.Controls/Parsers/ILayoutLoader.cs index d3671b27..31acf1da 100644 --- a/Botticelli.Framework.Controls/Parsers/ILayoutLoader.cs +++ b/Botticelli.Controls/Parsers/ILayoutLoader.cs @@ -1,4 +1,4 @@ -namespace Botticelli.Framework.Controls.Parsers; +namespace Botticelli.Controls.Parsers; /// /// Gets layout from config file diff --git a/Botticelli.Framework.Controls/Parsers/ILayoutParser.cs b/Botticelli.Controls/Parsers/ILayoutParser.cs similarity index 57% rename from Botticelli.Framework.Controls/Parsers/ILayoutParser.cs rename to Botticelli.Controls/Parsers/ILayoutParser.cs index 2c2cda47..af108fb8 100644 --- a/Botticelli.Framework.Controls/Parsers/ILayoutParser.cs +++ b/Botticelli.Controls/Parsers/ILayoutParser.cs @@ -1,6 +1,6 @@ -using Botticelli.Framework.Controls.Layouts; +using Botticelli.Controls.Layouts; -namespace Botticelli.Framework.Controls.Parsers; +namespace Botticelli.Controls.Parsers; public interface ILayoutParser { diff --git a/Botticelli.Framework.Controls/Parsers/ILayoutSupplier.cs b/Botticelli.Controls/Parsers/ILayoutSupplier.cs similarity index 73% rename from Botticelli.Framework.Controls/Parsers/ILayoutSupplier.cs rename to Botticelli.Controls/Parsers/ILayoutSupplier.cs index 918ddd83..c5540c43 100644 --- a/Botticelli.Framework.Controls/Parsers/ILayoutSupplier.cs +++ b/Botticelli.Controls/Parsers/ILayoutSupplier.cs @@ -1,6 +1,6 @@ -using Botticelli.Framework.Controls.Layouts; +using Botticelli.Controls.Layouts; -namespace Botticelli.Framework.Controls.Parsers; +namespace Botticelli.Controls.Parsers; /// /// Supplier is responsible for conversion of Layout into messenger-specific controls (for example, ReplyMarkup in diff --git a/Botticelli.Framework.Controls/Parsers/JsonLayoutParser.cs b/Botticelli.Controls/Parsers/JsonLayoutParser.cs similarity index 89% rename from Botticelli.Framework.Controls/Parsers/JsonLayoutParser.cs rename to Botticelli.Controls/Parsers/JsonLayoutParser.cs index 9f967725..5f3f026b 100644 --- a/Botticelli.Framework.Controls/Parsers/JsonLayoutParser.cs +++ b/Botticelli.Controls/Parsers/JsonLayoutParser.cs @@ -1,9 +1,9 @@ using System.Text.Json; -using Botticelli.Framework.Controls.BasicControls; -using Botticelli.Framework.Controls.Exceptions; -using Botticelli.Framework.Controls.Layouts; +using Botticelli.Controls.BasicControls; +using Botticelli.Controls.Exceptions; +using Botticelli.Controls.Layouts; -namespace Botticelli.Framework.Controls.Parsers; +namespace Botticelli.Controls.Parsers; public class JsonLayoutParser : ILayoutParser { @@ -48,7 +48,7 @@ public ILayout Parse(string jsonText) if (itemElement.TryGetProperty("Specials", out var messengerSpecific)) if (item.Control != null) item.Control.MessengerSpecificParams = - messengerSpecific.Deserialize>>(); + messengerSpecific.Deserialize>>(); row.AddItem(item); } diff --git a/Botticelli.Framework.Controls/Parsers/LayoutLoader.cs b/Botticelli.Controls/Parsers/LayoutLoader.cs similarity index 88% rename from Botticelli.Framework.Controls/Parsers/LayoutLoader.cs rename to Botticelli.Controls/Parsers/LayoutLoader.cs index e8959611..5f1d6fc9 100644 --- a/Botticelli.Framework.Controls/Parsers/LayoutLoader.cs +++ b/Botticelli.Controls/Parsers/LayoutLoader.cs @@ -1,6 +1,6 @@ -using Botticelli.Framework.Controls.Exceptions; +using Botticelli.Controls.Exceptions; -namespace Botticelli.Framework.Controls.Parsers; +namespace Botticelli.Controls.Parsers; public class LayoutLoader(TLayoutParser parser, TLayoutSupplier supplier) : ILayoutLoader diff --git a/Botticelli.Framework.Common/BotEventArgs.cs b/Botticelli.Framework.Common/BotEventArgs.cs index c478bbce..e281cd84 100644 --- a/Botticelli.Framework.Common/BotEventArgs.cs +++ b/Botticelli.Framework.Common/BotEventArgs.cs @@ -1,21 +1,7 @@ using System; -using Botticelli.Interfaces; namespace Botticelli.Framework.Events; -public delegate void MessengerSpecificEventHandler(object sender, MessengerSpecificBotEventArgs e) - where T : IBot; - -public delegate void MsgReceivedEventHandler(object sender, MessageReceivedBotEventArgs e); - -public delegate void MsgRemovedEventHandler(object sender, MessageRemovedBotEventArgs e); - -public delegate void MsgSentEventHandler(object sender, MessageSentBotEventArgs e); - -public delegate void StartedEventHandler(object sender, StartedBotEventArgs e); - -public delegate void StoppedEventHandler(object sender, StoppedBotEventArgs e); - public class BotEventArgs : EventArgs { public DateTime DateTime { get; } = DateTime.Now; diff --git a/Botticelli.Framework.Common/MessageRemovedBotEventArgs.cs b/Botticelli.Framework.Common/MessageRemovedBotEventArgs.cs index a17b3852..2f1dd698 100644 --- a/Botticelli.Framework.Common/MessageRemovedBotEventArgs.cs +++ b/Botticelli.Framework.Common/MessageRemovedBotEventArgs.cs @@ -2,5 +2,5 @@ public class MessageRemovedBotEventArgs : BotEventArgs { - public required string MessageUid { get; set; } + public required string? MessageUid { get; set; } } \ No newline at end of file diff --git a/Botticelli.Framework.Common/NewChatMembersBotEventArgs.cs b/Botticelli.Framework.Common/NewChatMembersBotEventArgs.cs new file mode 100644 index 00000000..48ada5bc --- /dev/null +++ b/Botticelli.Framework.Common/NewChatMembersBotEventArgs.cs @@ -0,0 +1,8 @@ +using Botticelli.Shared.ValueObjects; + +namespace Botticelli.Framework.Events; + +public class NewChatMembersBotEventArgs : BotEventArgs +{ + public required User User { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Framework.Common/SharedContactBotEventArgs.cs b/Botticelli.Framework.Common/SharedContactBotEventArgs.cs new file mode 100644 index 00000000..a3151e66 --- /dev/null +++ b/Botticelli.Framework.Common/SharedContactBotEventArgs.cs @@ -0,0 +1,8 @@ +using Botticelli.Shared.ValueObjects; + +namespace Botticelli.Framework.Events; + +public class SharedContactBotEventArgs : BotEventArgs +{ + public required Contact Contact { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/MonthBackwardCommand.cs b/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/MonthBackwardCommand.cs deleted file mode 100644 index faa39bad..00000000 --- a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/MonthBackwardCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; - -public class MonthBackwardCommand : BaseCalendarCommand -{ -} \ No newline at end of file diff --git a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/MonthForwardCommand.cs b/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/MonthForwardCommand.cs deleted file mode 100644 index 76c637fa..00000000 --- a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/MonthForwardCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; - -public class MonthForwardCommand : BaseCalendarCommand -{ -} \ No newline at end of file diff --git a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/SetDateCommand.cs b/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/SetDateCommand.cs deleted file mode 100644 index 97e09014..00000000 --- a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/SetDateCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; - -public class SetDateCommand : BaseCalendarCommand -{ -} \ No newline at end of file diff --git a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/YearBackwardCommand.cs b/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/YearBackwardCommand.cs deleted file mode 100644 index 6534f391..00000000 --- a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/YearBackwardCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; - -public class YearBackwardCommand : BaseCalendarCommand -{ -} \ No newline at end of file diff --git a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/YearForwardCommand.cs b/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/YearForwardCommand.cs deleted file mode 100644 index 5a98ffb3..00000000 --- a/Botticelli.Framework.Controls.Layouts/Commands/InlineCalendar/YearForwardCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; - -public class YearForwardCommand : BaseCalendarCommand -{ -} \ No newline at end of file diff --git a/Botticelli.Framework.Controls/BasicControls/TextButton.cs b/Botticelli.Framework.Controls/BasicControls/TextButton.cs deleted file mode 100644 index 41aaab8b..00000000 --- a/Botticelli.Framework.Controls/BasicControls/TextButton.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Botticelli.Framework.Controls.BasicControls; - -public class TextButton : Button -{ -} \ No newline at end of file diff --git a/Botticelli.Framework.Telegram/Botticelli.Framework.Telegram.csproj b/Botticelli.Framework.Telegram/Botticelli.Framework.Telegram.csproj index d3afe2b3..cfdb78dc 100644 --- a/Botticelli.Framework.Telegram/Botticelli.Framework.Telegram.csproj +++ b/Botticelli.Framework.Telegram/Botticelli.Framework.Telegram.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli.Framework.Telegram BotticelliBots new_logo_compact.png @@ -24,12 +24,12 @@ - + - + @@ -37,4 +37,9 @@ \ + + + C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.17\Microsoft.AspNetCore.Http.Abstractions.dll + + \ No newline at end of file diff --git a/Botticelli.Framework.Telegram/Builders/ITelegramBotBuilder.cs b/Botticelli.Framework.Telegram/Builders/ITelegramBotBuilder.cs new file mode 100644 index 00000000..a6add4ef --- /dev/null +++ b/Botticelli.Framework.Telegram/Builders/ITelegramBotBuilder.cs @@ -0,0 +1,35 @@ +using Botticelli.Bot.Data.Settings; +using Botticelli.Client.Analytics.Settings; +using Botticelli.Framework.Builders; +using Botticelli.Framework.Options; +using Botticelli.Framework.Telegram.Decorators; +using Botticelli.Framework.Telegram.Handlers; +using Botticelli.Framework.Telegram.Options; +using Microsoft.Extensions.DependencyInjection; + +namespace Botticelli.Framework.Telegram.Builders; + +public interface ITelegramBotBuilder where TBot : TelegramBot where TBotBuilder : BotBuilder +{ + ITelegramBotBuilder AddSubHandler() + where T : class, IBotUpdateSubHandler; + + ITelegramBotBuilder AddToken(string botToken); + ITelegramBotBuilder AddClient(TelegramClientDecoratorBuilder builder); + TBot? Build(); + BotBuilder AddServices(IServiceCollection services); + BotBuilder AddAnalyticsSettings(AnalyticsClientSettingsBuilder clientSettingsBuilder); + BotBuilder AddBotDataAccessSettings(DataAccessSettingsBuilder botDataAccessBuilder); + BotBuilder AddOnMessageSent(BaseBot.MsgSentEventHandler handler); + BotBuilder AddOnMessageReceived(BaseBot.MsgReceivedEventHandler handler); + BotBuilder AddOnMessageRemoved(BaseBot.MsgRemovedEventHandler handler); + BotBuilder AddNewChatMembers(BaseBot.NewChatMembersEventHandler handler); + BotBuilder AddSharedContact(BaseBot.ContactSharedEventHandler handler); + + static abstract ITelegramBotBuilder Instance(IServiceCollection services, + ServerSettingsBuilder serverSettingsBuilder, + BotSettingsBuilder settingsBuilder, + DataAccessSettingsBuilder dataAccessSettingsBuilder, + AnalyticsClientSettingsBuilder analyticsClientSettingsBuilder, + bool isStandalone); +} \ No newline at end of file diff --git a/Botticelli.Framework.Telegram/Builders/TelegramBotBuilder.cs b/Botticelli.Framework.Telegram/Builders/TelegramBotBuilder.cs index e25fca67..6d1c3575 100644 --- a/Botticelli.Framework.Telegram/Builders/TelegramBotBuilder.cs +++ b/Botticelli.Framework.Telegram/Builders/TelegramBotBuilder.cs @@ -5,8 +5,8 @@ using Botticelli.Bot.Utils.TextUtils; using Botticelli.Client.Analytics; using Botticelli.Client.Analytics.Settings; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Builders; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Framework.Extensions; using Botticelli.Framework.Options; using Botticelli.Framework.Security; @@ -14,10 +14,10 @@ using Botticelli.Framework.Telegram.Decorators; using Botticelli.Framework.Telegram.Handlers; using Botticelli.Framework.Telegram.HostedService; +using Botticelli.Framework.Telegram.Http; using Botticelli.Framework.Telegram.Layout; using Botticelli.Framework.Telegram.Options; using Botticelli.Framework.Telegram.Utils; -using Botticelli.Interfaces; using Botticelli.Shared.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -26,34 +26,69 @@ namespace Botticelli.Framework.Telegram.Builders; -public class TelegramBotBuilder : BotBuilder, TBot> - where TBot : TelegramBot +/// +/// +/// +/// +public abstract class TelegramBotBuilder(bool isStandalone) + : TelegramBotBuilder>(isStandalone) + where TBot : TelegramBot { +} + +/// +/// Builder for a non-standalone Telegram bot +/// +/// +/// +public class TelegramBotBuilder : BotBuilder + where TBot : TelegramBot + where TBotBuilder : BotBuilder +{ + private readonly bool _isStandalone; private readonly List> _subHandlers = []; + private string? _botToken; private TelegramClientDecoratorBuilder _builder = null!; - private TelegramClientDecorator _client = null!; - private TelegramBotSettings? BotSettings { get; set; } - public static TelegramBotBuilder Instance(IServiceCollection services, - ServerSettingsBuilder serverSettingsBuilder, - BotSettingsBuilder settingsBuilder, - DataAccessSettingsBuilder dataAccessSettingsBuilder, - AnalyticsClientSettingsBuilder analyticsClientSettingsBuilder) + public TelegramBotBuilder() : this(false) + { + + } + + public TelegramBotBuilder(bool isStandalone) { - return new TelegramBotBuilder() - .AddServices(services) - .AddServerSettings(serverSettingsBuilder) - .AddAnalyticsSettings(analyticsClientSettingsBuilder) - .AddBotDataAccessSettings(dataAccessSettingsBuilder) - .AddBotSettings(settingsBuilder); + _isStandalone = isStandalone; } - public TelegramBotBuilder AddSubHandler() - where T : class, IBotUpdateSubHandler + protected TelegramBotSettings? BotSettings { get; set; } + protected BotData.Entities.Bot.BotData? BotData { get; set; } + + public static TBotBuilderNew? Instance(IServiceCollection services, + ServerSettingsBuilder serverSettingsBuilder, + BotSettingsBuilder settingsBuilder, + DataAccessSettingsBuilder dataAccessSettingsBuilder, + AnalyticsClientSettingsBuilder analyticsClientSettingsBuilder, + bool isStandalone) + where TBotBuilderNew : BotBuilder + { + var botBuilder = Activator.CreateInstance(typeof(TBotBuilderNew), [isStandalone]) as TBotBuilderNew; + + (botBuilder as TelegramBotBuilder>) + .AddBotSettings(settingsBuilder) + .AddServerSettings(serverSettingsBuilder) + .AddAnalyticsSettings(analyticsClientSettingsBuilder) + .AddBotDataAccessSettings(dataAccessSettingsBuilder) + .AddServices(services); + + return botBuilder; + } + + public TelegramBotBuilder AddSubHandler() + where T : class, IBotUpdateSubHandler { Services.NotNull(); - Services!.AddSingleton(); + Services.AddSingleton(); _subHandlers.Add(sp => { @@ -66,92 +101,125 @@ public TelegramBotBuilder AddSubHandler() return this; } - public TelegramBotBuilder AddClient(TelegramClientDecoratorBuilder builder) + public TelegramBotBuilder AddToken(string botToken) + { + _botToken = botToken; + + return this; + } + + public TelegramBotBuilder AddClient(TelegramClientDecoratorBuilder builder) { _builder = builder; return this; } - protected override TBot? InnerBuild() + protected override TBot? InnerBuild(IServiceProvider serviceProvider) { - Services!.AddSingleton(ServerSettingsBuilder.Build()); + ApplyMigrations(serviceProvider); + + foreach (var sh in _subHandlers) sh.Invoke(serviceProvider); + + if (Activator.CreateInstance(typeof(TBot), + _builder.Build(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService>(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetService()) is not TBot bot) + throw new InvalidDataException($"{nameof(bot)} shouldn't be null!"); + + AddEvents(bot); + + return bot; + } - Services!.AddHttpClient() - .AddServerCertificates(BotSettings); - Services!.AddHostedService(); + public TelegramBotBuilder Prepare() + { + if (!_isStandalone) + { + Services.AddSingleton(ServerSettingsBuilder!.Build()); - Services!.AddHttpClient() - .AddServerCertificates(BotSettings); - Services!.AddHostedService(); + Services.AddHttpClient() + .AddServerCertificates(BotSettings); + Services.AddHostedService(); - Services!.AddHttpClient>() - .AddServerCertificates(BotSettings); - Services!.AddHostedService>>(); - Services!.AddHostedService>>(); + Services.AddHttpClient() + .AddServerCertificates(BotSettings); + Services.AddHostedService(); + + Services.AddHostedService(); + } - Services!.AddHostedService(); var botId = BotDataUtils.GetBotId(); if (botId == null) throw new InvalidDataException($"{nameof(botId)} shouldn't be null!"); #region Metrics - var metricsPublisher = new MetricsPublisher(AnalyticsClientSettingsBuilder.Build()); - var metricsProcessor = new MetricsProcessor(metricsPublisher); - Services!.AddSingleton(metricsPublisher); - Services!.AddSingleton(metricsProcessor); + if (!_isStandalone) + { + var metricsPublisher = new MetricsPublisher(AnalyticsClientSettingsBuilder!.Build()); + var metricsProcessor = new MetricsProcessor(metricsPublisher); + Services.AddSingleton(metricsPublisher); + Services.AddSingleton(metricsProcessor); + } #endregion #region Data - Services!.AddDbContext(o => - o.UseSqlite($"Data source={BotDataAccessSettingsBuilder.Build().ConnectionString}")); - Services!.AddScoped(); + Services.AddDbContext(o => + o.UseSqlite($"Data source={BotDataAccessSettingsBuilder!.Build().ConnectionString}")); + Services.AddScoped(); #endregion #region TextTransformer - Services!.AddTransient(); + Services.AddTransient(); #endregion - if (BotSettings?.UseThrottling is true) _builder.AddThrottler(new Throttler()); - _client = _builder.Build(); - _client.Timeout = TimeSpan.FromMilliseconds(BotSettings?.Timeout ?? 10000); + if (BotSettings?.UseThrottling is true) + _builder.AddThrottler(new OutcomeThrottlingDelegatingHandler()); - Services!.AddSingleton, ReplyTelegramLayoutSupplier>() - .AddBotticelliFramework() - .AddSingleton(); + if (!string.IsNullOrWhiteSpace(_botToken)) _builder.AddToken(_botToken); - Services!.AddSingleton(ServerSettingsBuilder.Build()); + var client = _builder.Build(); - var sp = Services!.BuildServiceProvider(); - foreach (var sh in _subHandlers) sh.Invoke(sp); + client.NotNull(); - ApplyMigrations(sp); + client!.Timeout = TimeSpan.FromMilliseconds(BotSettings?.Timeout ?? 10000); - var telegramBot = Activator.CreateInstance(typeof(TBot), - _client, - sp.GetRequiredService(), - sp.GetRequiredService>(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService()) as TBot; + Services.AddSingleton, ReplyTelegramLayoutSupplier>() + .AddBotticelliFramework() + .AddSingleton() + .AddSingleton(client); + + return this; + } - return telegramBot; + private void AddEvents(TBot bot) + { + bot.MessageSent += MessageSent; + bot.MessageReceived += MessageReceived; + bot.MessageRemoved += MessageRemoved; + bot.ContactShared += SharedContact; + bot.NewChatMembers += NewChatMembers; } - public override TelegramBotBuilder AddBotSettings(BotSettingsBuilder settingsBuilder) + protected TelegramBotBuilder AddBotSettings( + BotSettingsBuilder settingsBuilder) + where TBotSettings : BotSettings, new() { BotSettings = settingsBuilder.Build() as TelegramBotSettings ?? throw new InvalidOperationException(); return this; } - private void ApplyMigrations(IServiceProvider sp) + private static void ApplyMigrations(IServiceProvider sp) { sp.GetRequiredService().Database.Migrate(); } diff --git a/Botticelli.Framework.Telegram/Builders/TelegramStandaloneBotBuilder.cs b/Botticelli.Framework.Telegram/Builders/TelegramStandaloneBotBuilder.cs new file mode 100644 index 00000000..7460b2bc --- /dev/null +++ b/Botticelli.Framework.Telegram/Builders/TelegramStandaloneBotBuilder.cs @@ -0,0 +1,63 @@ +using System.Configuration; +using Botticelli.Bot.Data.Settings; +using Botticelli.Framework.Options; +using Botticelli.Framework.Security; +using Botticelli.Framework.Services; +using Botticelli.Framework.Telegram.Options; +using Botticelli.Shared.API.Admin.Responses; +using Botticelli.Shared.Constants; +using Microsoft.Extensions.DependencyInjection; + +namespace Botticelli.Framework.Telegram.Builders; + +/// +/// Builder for a standalone telegram bot +/// +/// +public class TelegramStandaloneBotBuilder : TelegramBotBuilder> + where TBot : TelegramBot +{ + private TelegramStandaloneBotBuilder() : base(true) + { + } + + public static TelegramStandaloneBotBuilder Instance(IServiceCollection services, + BotSettingsBuilder settingsBuilder, + DataAccessSettingsBuilder dataAccessSettingsBuilder) + { + return (TelegramStandaloneBotBuilder) new TelegramStandaloneBotBuilder() + .AddBotSettings(settingsBuilder) + .AddServices(services) + .AddBotDataAccessSettings(dataAccessSettingsBuilder); + } + + public TelegramStandaloneBotBuilder AddBotData(BotDataSettingsBuilder dataBuilder) + { + var settings = dataBuilder.Build(); + + BotData = new BotData.Entities.Bot.BotData + { + BotId = settings?.BotId ?? throw new ConfigurationErrorsException("No BotId in bot settings!"), + Status = BotStatus.Unlocked, + Type = BotType.Telegram, + BotKey = settings.BotKey ?? throw new ConfigurationErrorsException("No BotKey in bot settings!") + }; + + AddToken(BotData.BotKey); + + return this; + } + + protected override TBot? InnerBuild(IServiceProvider serviceProvider) + { + Services.AddHttpClient() + .AddServerCertificates(BotSettings); + + if (BotData == null) throw new ConfigurationErrorsException("BotData is null!"); + + Services.AddHostedService() + .AddSingleton(BotData); + + return base.InnerBuild(serviceProvider); + } +} \ No newline at end of file diff --git a/Botticelli.Framework.Telegram/Decorators/TelegramClientDecorator.cs b/Botticelli.Framework.Telegram/Decorators/TelegramClientDecorator.cs index c1025224..3c005c9a 100644 --- a/Botticelli.Framework.Telegram/Decorators/TelegramClientDecorator.cs +++ b/Botticelli.Framework.Telegram/Decorators/TelegramClientDecorator.cs @@ -1,4 +1,5 @@ -using Telegram.Bot; +using Botticelli.Framework.Exceptions; +using Telegram.Bot; using Telegram.Bot.Args; using Telegram.Bot.Exceptions; using Telegram.Bot.Requests.Abstractions; @@ -12,15 +13,18 @@ namespace Botticelli.Framework.Telegram.Decorators; public class TelegramClientDecorator : ITelegramBotClient { private readonly HttpClient? _httpClient; - private readonly IThrottler? _throttler; private TelegramBotClient? _innerClient; private TelegramBotClientOptions _options; + #region Limits + private const int MessagesInSecond = 30; + private DateTime? _prev; + private long? _sentMessageCount = 0; + #endregion Limits + internal TelegramClientDecorator(TelegramBotClientOptions options, - IThrottler? throttler, HttpClient? httpClient = null) { - _throttler = throttler; _options = options; _httpClient = httpClient; _innerClient = !string.IsNullOrWhiteSpace(options.Token) ? new TelegramBotClient(options, httpClient) : null; @@ -32,11 +36,21 @@ public async Task SendRequest(IRequest request, { try { - if (_throttler != null) - return await _throttler.Throttle(async () => await _innerClient?.SendRequest(request, cancellationToken)!, - cancellationToken); - - return await _innerClient?.SendRequest(request, cancellationToken)!; + _prev ??= DateTime.UtcNow; + _sentMessageCount ??= 0; + var deltaT = (DateTime.UtcNow - _prev).Value; + + if (_sentMessageCount < MessagesInSecond && deltaT < TimeSpan.FromSeconds(1.0)) + return await _innerClient?.SendRequest(request, cancellationToken)!; + else + { + await Task.Delay(deltaT, cancellationToken); + + _sentMessageCount = 0; + _prev = DateTime.UtcNow; + + return await _innerClient?.SendRequest(request, cancellationToken); + } } catch (ApiRequestException ex) { @@ -44,20 +58,10 @@ public async Task SendRequest(IRequest request, throw; } - } - - [Obsolete("Use SendRequest")] - public Task MakeRequest(IRequest request, - CancellationToken cancellationToken = new()) - { - return SendRequest(request, cancellationToken); - } - - [Obsolete("Use SendRequest")] - public async Task MakeRequestAsync(IRequest request, - CancellationToken cancellationToken = new()) - { - return await SendRequest(request, cancellationToken); + finally + { + ++_sentMessageCount; + } } public Task TestApi(CancellationToken cancellationToken = new()) diff --git a/Botticelli.Framework.Telegram/Decorators/TelegramClientDecoratorBuilder.cs b/Botticelli.Framework.Telegram/Decorators/TelegramClientDecoratorBuilder.cs index 9f404eba..ce87ab0a 100644 --- a/Botticelli.Framework.Telegram/Decorators/TelegramClientDecoratorBuilder.cs +++ b/Botticelli.Framework.Telegram/Decorators/TelegramClientDecoratorBuilder.cs @@ -1,4 +1,5 @@ using Botticelli.Framework.Options; +using Botticelli.Framework.Telegram.Http; using Botticelli.Framework.Telegram.Options; using Microsoft.Extensions.DependencyInjection; using Telegram.Bot; @@ -11,7 +12,7 @@ public class TelegramClientDecoratorBuilder private readonly BotSettingsBuilder _settingsBuilder; private HttpClient? _httpClient; private TelegramClientDecorator? _telegramClient; - private IThrottler? _throttler; + private OutcomeThrottlingDelegatingHandler? _throttler; private string? _token; private TelegramClientDecoratorBuilder(IServiceCollection services, @@ -27,7 +28,7 @@ public static TelegramClientDecoratorBuilder Instance(IServiceCollection service return new TelegramClientDecoratorBuilder(services, settingsBuilder); } - public TelegramClientDecoratorBuilder AddThrottler(IThrottler throttler) + public TelegramClientDecoratorBuilder AddThrottler(OutcomeThrottlingDelegatingHandler throttler) { _throttler = throttler; @@ -41,7 +42,7 @@ public TelegramClientDecoratorBuilder AddToken(string? token) return this; } - public TelegramClientDecorator Build() + public TelegramClientDecorator? Build() { _token ??= "11111111:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; @@ -49,9 +50,14 @@ public TelegramClientDecorator Build() if (_httpClient == null) { - var factory = _services.BuildServiceProvider().GetRequiredService(); + if (_throttler == null) + { + var factory = _services.BuildServiceProvider().GetRequiredService(); - _httpClient = factory.CreateClient(nameof(TelegramClientDecorator)); + _httpClient = factory.CreateClient(nameof(TelegramClientDecorator)); + } + else + _httpClient = new HttpClient(_throttler); } var botOptions = _settingsBuilder.Build(); @@ -61,7 +67,7 @@ public TelegramClientDecorator Build() RetryThreshold = 60, RetryCount = botOptions.RetryOnFailure }; - _telegramClient = new TelegramClientDecorator(clientOptions, _throttler, _httpClient); + _telegramClient = new TelegramClientDecorator(clientOptions, _httpClient); return _telegramClient; } diff --git a/Botticelli.Framework.Telegram/Decorators/Throttler.cs b/Botticelli.Framework.Telegram/Decorators/Throttler.cs index b4528778..c0802a00 100644 --- a/Botticelli.Framework.Telegram/Decorators/Throttler.cs +++ b/Botticelli.Framework.Telegram/Decorators/Throttler.cs @@ -15,7 +15,7 @@ public ValueTask Throttle(Func> action, CancellationToken ct) { var diff = DateTime.UtcNow - _prevDt; var randComponent = - TimeSpan.FromMilliseconds(_random.Next(-MaxDeviation.Milliseconds, MaxDeviation.Milliseconds)); + TimeSpan.FromMilliseconds(_random.Next((int)-MaxDeviation.TotalMilliseconds, (int)MaxDeviation.TotalMilliseconds)); var sumDelay = Delay + randComponent; if (diff < sumDelay) Task.Delay(sumDelay - diff, ct).WaitAsync(ct); diff --git a/Botticelli.Framework.Telegram/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Framework.Telegram/Extensions/ServiceCollectionExtensions.cs index 68dd2cd7..84b71ae2 100644 --- a/Botticelli.Framework.Telegram/Extensions/ServiceCollectionExtensions.cs +++ b/Botticelli.Framework.Telegram/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,7 @@ using System.Configuration; using Botticelli.Bot.Data.Settings; using Botticelli.Client.Analytics.Settings; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Options; using Botticelli.Framework.Telegram.Builders; using Botticelli.Framework.Telegram.Decorators; @@ -18,22 +18,24 @@ public static class ServiceCollectionExtensions { private static readonly BotSettingsBuilder SettingsBuilder = new(); private static readonly ServerSettingsBuilder ServerSettingsBuilder = new(); + private static readonly BotDataSettingsBuilder BotDataSettingsBuilder = new(); private static readonly AnalyticsClientSettingsBuilder AnalyticsClientOptionsBuilder = - new(); + new(); private static readonly DataAccessSettingsBuilder DataAccessSettingsBuilder = new(); - public static IServiceCollection AddTelegramBot(this IServiceCollection services, - IConfiguration configuration, - Action>? telegramBotBuilderFunc = null) + public static TelegramBotBuilder> AddTelegramBot(this IServiceCollection services, + IConfiguration configuration, + Action>>? telegramBotBuilderFunc = null) { - return AddTelegramBot(services, configuration, telegramBotBuilderFunc); + return AddTelegramBot>>(services, configuration, telegramBotBuilderFunc); } - public static IServiceCollection AddTelegramBot(this IServiceCollection services, - IConfiguration configuration, - Action>? telegramBotBuilderFunc = null) + public static TBotBuilder AddTelegramBot(this IServiceCollection services, + IConfiguration configuration, + Action? telegramBotBuilderFunc = null) + where TBotBuilder : TelegramBotBuilder> where TBot : TelegramBot { var telegramBotSettings = configuration @@ -55,27 +57,27 @@ public static IServiceCollection AddTelegramBot(this IServiceCollection se .GetSection(DataAccessSettings.Section) .Get() ?? throw new ConfigurationErrorsException($"Can't load configuration for {nameof(DataAccessSettings)}!"); - - return services.AddTelegramBot(telegramBotSettings, - analyticsClientSettings, - serverSettings, - dataAccessSettings, - telegramBotBuilderFunc); + + return services.AddTelegramBot( + o => o.Set(telegramBotSettings), + o => o.Set(analyticsClientSettings), + o => o.Set(serverSettings), + o => o.Set(dataAccessSettings), + telegramBotBuilderFunc); } - public static IServiceCollection AddTelegramBot(this IServiceCollection services, - TelegramBotSettings botSettings, - AnalyticsClientSettings analyticsClientSettings, - ServerSettings serverSettings, - DataAccessSettings dataAccessSettings, - Action>? telegramBotBuilderFunc = null) - where TBot : TelegramBot + public static TelegramBotBuilder> AddTelegramBot(this IServiceCollection services, + TelegramBotSettings botSettings, + AnalyticsClientSettings analyticsClientSettings, + ServerSettings serverSettings, + DataAccessSettings dataAccessSettings, + Action>>? telegramBotBuilderFunc = null) { - return services.AddTelegramBot(o => o.Set(botSettings), - o => o.Set(analyticsClientSettings), - o => o.Set(serverSettings), - o => o.Set(dataAccessSettings), - telegramBotBuilderFunc); + return services.AddTelegramBot>>(o => o.Set(botSettings), + o => o.Set(analyticsClientSettings), + o => o.Set(serverSettings), + o => o.Set(dataAccessSettings), + telegramBotBuilderFunc); } /// @@ -88,14 +90,39 @@ public static IServiceCollection AddTelegramBot(this IServiceCollection se /// /// /// - public static IServiceCollection AddTelegramBot(this IServiceCollection services, - Action> optionsBuilderFunc, - Action> analyticsOptionsBuilderFunc, - Action> serverSettingsBuilderFunc, - Action> dataAccessSettingsBuilderFunc, - Action>? telegramBotBuilderFunc = null) + public static TBotBuilder AddTelegramBot(this IServiceCollection services, + Action> optionsBuilderFunc, + Action> analyticsOptionsBuilderFunc, + Action> serverSettingsBuilderFunc, + Action> dataAccessSettingsBuilderFunc, + Action? telegramBotBuilderFunc = null) + where TBotBuilder : TelegramBotBuilder> where TBot : TelegramBot { + services.AddHttpClient(); + var botBuilder = InnerBuild(services, + optionsBuilderFunc, + analyticsOptionsBuilderFunc, + serverSettingsBuilderFunc, + dataAccessSettingsBuilderFunc, + telegramBotBuilderFunc); + + services.AddTelegramLayoutsSupport() + .AddSingleton(botBuilder); + + return botBuilder; + } + + static TBotBuilder InnerBuild(IServiceCollection services, + Action> optionsBuilderFunc, + Action> analyticsOptionsBuilderFunc, + Action> serverSettingsBuilderFunc, + Action> dataAccessSettingsBuilderFunc, + Action? telegramBotBuilderFunc) + where TBotBuilder : TelegramBotBuilder> + where TBot : TelegramBot + { + services.AddHttpClient(); optionsBuilderFunc(SettingsBuilder); serverSettingsBuilderFunc(ServerSettingsBuilder); analyticsOptionsBuilderFunc(AnalyticsClientOptionsBuilder); @@ -103,29 +130,95 @@ public static IServiceCollection AddTelegramBot(this IServiceCollection se var clientBuilder = TelegramClientDecoratorBuilder.Instance(services, SettingsBuilder); - var botBuilder = TelegramBotBuilder.Instance(services, - ServerSettingsBuilder, - SettingsBuilder, - DataAccessSettingsBuilder, - AnalyticsClientOptionsBuilder) - .AddClient(clientBuilder); - + var botBuilder = TelegramBotBuilder.Instance(services, + ServerSettingsBuilder, + SettingsBuilder, + DataAccessSettingsBuilder, + AnalyticsClientOptionsBuilder, + false); + + if (botBuilder == null) + throw new ApplicationException("bot builder is null!"); + + botBuilder.AddClient(clientBuilder) + .AddServices(services); + telegramBotBuilderFunc?.Invoke(botBuilder); + + services.AddSingleton(sp => sp.GetRequiredService>>() + .Build(sp)!); + + return botBuilder; + } + + public static TelegramStandaloneBotBuilder AddStandaloneTelegramBot(this IServiceCollection services, + IConfiguration configuration, + Action>>? telegramBotBuilderFunc = null) => + AddStandaloneTelegramBot(services, configuration, telegramBotBuilderFunc); - var bot = botBuilder.Build(); + public static TelegramStandaloneBotBuilder AddStandaloneTelegramBot(this IServiceCollection services, + IConfiguration configuration, + Action>>? telegramBotBuilderFunc = null) + where TBot : TelegramBot + { + services.AddHttpClient(); + var telegramBotSettings = configuration + .GetSection(TelegramBotSettings.Section) + .Get() ?? + throw new ConfigurationErrorsException( + $"Can't load configuration for {nameof(TelegramBotSettings)}!"); - return services.AddSingleton(bot) - .AddTelegramLayoutsSupport(); + var dataAccessSettings = configuration + .GetSection(DataAccessSettings.Section) + .Get() ?? + throw new ConfigurationErrorsException( + $"Can't load configuration for {nameof(DataAccessSettings)}!"); + + var botDataSettings = configuration + .GetSection(BotDataSettings.Section) + .Get() ?? + throw new ConfigurationErrorsException( + $"Can't load configuration for {nameof(BotDataSettings)}!"); + + return services.AddStandaloneTelegramBot( + botSettingsBuilder => botSettingsBuilder.Set(telegramBotSettings), + dataAccessSettingsBuilder => dataAccessSettingsBuilder.Set(dataAccessSettings), + botDataSettingsBuilder => botDataSettingsBuilder.Set(botDataSettings)); } - public static IServiceCollection AddTelegramLayoutsSupport(this IServiceCollection services) + public static TelegramStandaloneBotBuilder AddStandaloneTelegramBot(this IServiceCollection services, + Action> optionsBuilderFunc, + Action> dataAccessSettingsBuilderFunc, + Action> botDataSettingsBuilderFunc, + Action>? telegramBotBuilderFunc = null) + where TBot : TelegramBot { - return services.AddSingleton() - .AddSingleton, ReplyTelegramLayoutSupplier>() - .AddSingleton, InlineTelegramLayoutSupplier>() - .AddSingleton, LayoutLoader, ReplyKeyboardMarkup>>() - .AddSingleton, LayoutLoader, InlineKeyboardMarkup>>(); + services.AddHttpClient(); + optionsBuilderFunc(SettingsBuilder); + dataAccessSettingsBuilderFunc(DataAccessSettingsBuilder); + botDataSettingsBuilderFunc(BotDataSettingsBuilder); + + var clientBuilder = TelegramClientDecoratorBuilder.Instance(services, SettingsBuilder); + + var botBuilder = TelegramStandaloneBotBuilder.Instance(services, + SettingsBuilder, + DataAccessSettingsBuilder) + .AddBotData(BotDataSettingsBuilder) + .AddClient(clientBuilder); + + telegramBotBuilderFunc?.Invoke((TelegramStandaloneBotBuilder)botBuilder); + + services.AddTelegramLayoutsSupport(); + + return botBuilder as TelegramStandaloneBotBuilder; } + + public static IServiceCollection AddTelegramLayoutsSupport(this IServiceCollection services) => + services.AddSingleton() + .AddSingleton, ReplyTelegramLayoutSupplier>() + .AddSingleton, InlineTelegramLayoutSupplier>() + .AddSingleton, LayoutLoader, ReplyKeyboardMarkup>>() + .AddSingleton, LayoutLoader, InlineKeyboardMarkup>>(); } \ No newline at end of file diff --git a/Botticelli.Framework.Telegram/Handlers/BotUpdateHandler.cs b/Botticelli.Framework.Telegram/Handlers/BotUpdateHandler.cs index a15f7329..806ee3ab 100644 --- a/Botticelli.Framework.Telegram/Handlers/BotUpdateHandler.cs +++ b/Botticelli.Framework.Telegram/Handlers/BotUpdateHandler.cs @@ -6,18 +6,18 @@ using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using Contact = Botticelli.Shared.ValueObjects.Contact; using Message = Botticelli.Shared.ValueObjects.Message; using Poll = Botticelli.Shared.ValueObjects.Poll; using User = Botticelli.Shared.ValueObjects.User; namespace Botticelli.Framework.Telegram.Handlers; -public class BotUpdateHandler : IBotUpdateHandler +public class BotUpdateHandler(ILogger logger, IServiceProvider serviceProvider) : IBotUpdateHandler { private readonly MemoryCacheEntryOptions _entryOptions - = new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromHours(24)); - - private readonly ILogger _logger; + = new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromHours(24)); private readonly MemoryCache _memoryCache = new(new MemoryCacheOptions { @@ -26,149 +26,99 @@ private readonly MemoryCacheEntryOptions _entryOptions private readonly List _subHandlers = []; - public BotUpdateHandler(ILogger logger) - { - _logger = logger; - } - public async Task HandleUpdateAsync(ITelegramBotClient botClient, - Update update, - CancellationToken cancellationToken) + Update update, + CancellationToken cancellationToken) { try { - // caching updates in order to avoid message "cloning" + // caching updates to avoid message "cloning" if (_memoryCache.TryGetValue(update.Id, out _)) return; _memoryCache.Set(update.Id, update, _entryOptions); - _logger.LogDebug($"{nameof(HandleUpdateAsync)}() started..."); + logger.LogDebug($"{nameof(HandleUpdateAsync)}() started..."); - var botMessage = update.Message; + var botMessage = update.Message ?? update.ChannelPost; Message? botticelliMessage = null; if (botMessage == null) { - if (update.CallbackQuery != null) - { - botMessage = update.CallbackQuery?.Message; - - if (botMessage == null) - { - _logger.LogError($"{nameof(HandleUpdateAsync)}() {nameof(botMessage)} is null!"); - - return; - } + if (ProcessCallback(update, ref botticelliMessage)) + return; - botticelliMessage = new Message - { - ChatIdInnerIdLinks = new Dictionary> - { - { - update.CallbackQuery?.Message.Chat?.Id.ToString(), - [update.CallbackQuery.Message?.MessageId.ToString()] - } - }, - ChatIds = [update.CallbackQuery?.Message.Chat.Id.ToString()], - CallbackData = update.CallbackQuery?.Data ?? string.Empty, - CreatedAt = update.Message?.Date ?? DateTime.Now, - LastModifiedAt = update.Message?.Date ?? DateTime.Now, - From = new User - { - Id = update.CallbackQuery?.From.Id.ToString(), - Name = update.CallbackQuery?.From.FirstName, - Surname = update.CallbackQuery?.From.LastName, - Info = string.Empty, - IsBot = update.CallbackQuery?.From.IsBot, - NickName = update.CallbackQuery?.From.Username - } - }; - } - - if (update.Poll != null) - botticelliMessage = new Message - { - Subject = string.Empty, - Body = string.Empty, - Poll = new Poll - { - Id = update.Poll.Id, - IsAnonymous = update.Poll.IsAnonymous, - Question = update.Poll.Question, - Type = update.Poll.Type.ToLower() == "regular" ? Poll.PollType.Regular : Poll.PollType.Quiz, - Variants = update.Poll.Options.Select(o => new ValueTuple(o.Text, o.VoterCount)), - CorrectAnswerId = update.Poll.CorrectOptionId - } - }; + botticelliMessage = ProcessPoll(update, botticelliMessage); } else { - botticelliMessage = new Message(botMessage.MessageId.ToString()) - { - ChatIdInnerIdLinks = new Dictionary> - {{botMessage.Chat.Id.ToString(), [botMessage.MessageId.ToString()]}}, - ChatIds = [botMessage.Chat.Id.ToString()], - Subject = string.Empty, - Body = botMessage.Text ?? string.Empty, - LastModifiedAt = botMessage.Date, - Attachments = new List(5), - CreatedAt = botMessage.Date, - From = new User - { - Id = botMessage.From?.Id.ToString(), - Name = botMessage.From?.FirstName, - Surname = botMessage.From?.LastName, - Info = string.Empty, - IsBot = botMessage.From?.IsBot, - NickName = botMessage.From?.Username - }, - ForwardedFrom = new User - { - Id = botMessage.ForwardFrom?.Id.ToString(), - Name = botMessage.ForwardFrom?.FirstName, - Surname = botMessage.ForwardFrom?.LastName, - Info = string.Empty, - IsBot = botMessage.ForwardFrom?.IsBot, - NickName = botMessage.ForwardFrom?.Username - }, - Location = botMessage.Location != null ? - new GeoLocation - { - Latitude = (decimal) botMessage.Location.Latitude, - Longitude = (decimal) botMessage.Location.Longitude - } : - null - }; + botticelliMessage = ProcessOrdinaryMessage(botMessage); + ProcessNewChatMembers(botClient, botMessage); + ProcessContact(botMessage); } foreach (var subHandler in _subHandlers) await subHandler.Process(botClient, update, cancellationToken); if (botticelliMessage != null) { - await Process(botticelliMessage, cancellationToken); + await ProcessInProcessors(botticelliMessage, cancellationToken); MessageReceived?.Invoke(this, - new MessageReceivedBotEventArgs - { - Message = botticelliMessage - }); + new MessageReceivedBotEventArgs + { + Message = botticelliMessage + }); } - _logger.LogDebug($"{nameof(HandleUpdateAsync)}() finished..."); + logger.LogDebug($"{nameof(HandleUpdateAsync)}() finished..."); } catch (Exception ex) { - _logger.LogError(ex, $"{nameof(HandleUpdateAsync)}() error"); + logger.LogError(ex, $"{nameof(HandleUpdateAsync)}() error"); + } + } + + private void ProcessContact(global::Telegram.Bot.Types.Message? botMessage) + { + if (botMessage?.Contact != null) + ContactShared?.Invoke(this, new SharedContactBotEventArgs + { + Contact = new Contact + { + Phone = botMessage.Contact.PhoneNumber, + Name = botMessage.Contact.FirstName, + Surname = botMessage.Contact.LastName + } + }); + } + + private void ProcessNewChatMembers(ITelegramBotClient botClient, global::Telegram.Bot.Types.Message botMessage) + { + foreach (var newChatMember in botMessage.NewChatMembers ?? []) + { + var @event = new NewChatMembersBotEventArgs + { + User = new User + { + Id = newChatMember.Id.ToString(), + Name = newChatMember.FirstName, + Surname = newChatMember.LastName, + Info = string.Empty, + NickName = newChatMember.Username, + IsBot = newChatMember.IsBot + } + }; + + NewChatMembers?.Invoke(botClient, @event); } } public Task HandleErrorAsync(ITelegramBotClient botClient, - Exception exception, - HandleErrorSource source, - CancellationToken cancellationToken) + Exception exception, + HandleErrorSource source, + CancellationToken cancellationToken) { - _logger.LogError($"{nameof(HandleErrorAsync)}() error: {exception.Message}", exception); + logger.LogError("{HandleErrorAsyncName}() error: {ExceptionMessage} exception: {Exception}", nameof(HandleErrorAsync), exception.Message, exception); return Task.CompletedTask; } @@ -179,30 +129,132 @@ public void AddSubHandler(T subHandler) where T : IBotUpdateSubHandler } public event IBotUpdateHandler.MsgReceivedEventHandler? MessageReceived; + public event IBotUpdateHandler.NewChatMembersHandler? NewChatMembers; + public event IBotUpdateHandler.ContactSharedHandler? ContactShared; + + private static Message ProcessOrdinaryMessage(global::Telegram.Bot.Types.Message botMessage) + { + var botticelliMessage = new Message(botMessage.MessageId.ToString()) + { + ChatIdInnerIdLinks = new Dictionary> + { { botMessage.Chat.Id.ToString(), [botMessage.MessageId.ToString()] } }, + ChatIds = [botMessage.Chat.Id.ToString()], + Subject = string.Empty, + Body = botMessage.Text ?? string.Empty, + LastModifiedAt = botMessage.Date, + Attachments = new List(5), + CreatedAt = botMessage.Date, + From = new User + { + Id = botMessage.From?.Id.ToString(), + Name = botMessage.From?.FirstName, + Surname = botMessage.From?.LastName, + Info = string.Empty, + IsBot = botMessage.From?.IsBot, + NickName = botMessage.From?.Username + }, + ForwardedFrom = new User + { + Id = botMessage.ForwardFrom?.Id.ToString(), + Name = botMessage.ForwardFrom?.FirstName, + Surname = botMessage.ForwardFrom?.LastName, + Info = string.Empty, + IsBot = botMessage.ForwardFrom?.IsBot, + NickName = botMessage.ForwardFrom?.Username + }, + Location = botMessage.Location != null + ? new GeoLocation + { + Latitude = (decimal)botMessage.Location.Latitude, + Longitude = (decimal)botMessage.Location.Longitude + } + : null + }; + return botticelliMessage; + } + + private static Message? ProcessPoll(Update update, Message? botticelliMessage) + { + if (update.Poll != null) + botticelliMessage = new Message + { + Subject = string.Empty, + Body = string.Empty, + Poll = new Poll + { + Id = update.Poll.Id, + IsAnonymous = update.Poll.IsAnonymous, + Question = update.Poll.Question, + Type = update.Poll.Type == PollType.Regular ? Poll.PollType.Regular : Poll.PollType.Quiz, + Variants = update.Poll.Options.Select(o => new ValueTuple(o.Text, o.VoterCount)), + CorrectAnswerId = update.Poll.CorrectOptionId + } + }; + return botticelliMessage; + } + + private bool ProcessCallback(Update update, ref Message? botticelliMessage) + { + if (update.CallbackQuery == null) + return false; + + if (update.CallbackQuery?.Message == null) + { + logger.LogError($"{nameof(HandleUpdateAsync)}() callback message is null!"); + + return true; + } + + botticelliMessage = new Message + { + ChatIdInnerIdLinks = new Dictionary> + { + { + update.CallbackQuery?.Message.Chat.Id.ToString(), + [update.CallbackQuery.Message?.MessageId.ToString()] + } + }, + ChatIds = [update.CallbackQuery?.Message.Chat.Id.ToString()], + CallbackData = update.CallbackQuery?.Data ?? string.Empty, + CreatedAt = update.Message?.Date ?? DateTime.Now, + LastModifiedAt = update.Message?.Date ?? DateTime.Now, + From = new User + { + Id = update.CallbackQuery?.From.Id.ToString(), + Name = update.CallbackQuery?.From.FirstName, + Surname = update.CallbackQuery?.From.LastName, + Info = string.Empty, + IsBot = update.CallbackQuery?.From.IsBot, + NickName = update.CallbackQuery?.From.Username + } + }; + + return false; + } /// /// Processes requests /// /// /// - protected async Task Process(Message request, CancellationToken token) + protected async Task ProcessInProcessors(Message request, CancellationToken token) { - _logger.LogDebug($"{nameof(Process)}({request.Uid}) started..."); + logger.LogDebug("{ProcessInProcessorsName}({RequestUid}) started...", nameof(ProcessInProcessors), request.Uid); - if (token is {CanBeCanceled: true, IsCancellationRequested: true}) return; + if (token is { CanBeCanceled: true, IsCancellationRequested: true }) return; - var processorFactory = ProcessorFactoryBuilder.Build(); + var processorFactory = ProcessorFactoryBuilder.Build(serviceProvider); var clientNonChainedTasks = processorFactory.GetProcessors() - .Select(p => p.ProcessAsync(request, token)); + .Select(p => p.ProcessAsync(request, token)).ToList(); var clientChainedTasks = processorFactory.GetCommandChainProcessors() - .Select(p => p.ProcessAsync(request, token)); + .Select(p => p.ProcessAsync(request, token)).ToList(); var clientTasks = clientNonChainedTasks.Concat(clientChainedTasks).ToArray(); await Parallel.ForEachAsync(clientTasks, token, async (t, ct) => await t.WaitAsync(ct)); - _logger.LogDebug($"{nameof(Process)}({request.Uid}) finished..."); + logger.LogDebug("{ProcessInProcessorsName}({RequestUid}) finished...", nameof(ProcessInProcessors), request.Uid); } } \ No newline at end of file diff --git a/Botticelli.Framework.Telegram/Handlers/IBotUpdateHandler.cs b/Botticelli.Framework.Telegram/Handlers/IBotUpdateHandler.cs index be1e86e5..40143fc8 100644 --- a/Botticelli.Framework.Telegram/Handlers/IBotUpdateHandler.cs +++ b/Botticelli.Framework.Telegram/Handlers/IBotUpdateHandler.cs @@ -6,8 +6,12 @@ namespace Botticelli.Framework.Telegram.Handlers; public interface IBotUpdateHandler : IUpdateHandler { public delegate void MsgReceivedEventHandler(object sender, MessageReceivedBotEventArgs e); - + public delegate void NewChatMembersHandler(object sender, NewChatMembersBotEventArgs e); + public delegate void ContactSharedHandler(object sender, SharedContactBotEventArgs e); + public void AddSubHandler(T subHandler) where T : IBotUpdateSubHandler; public event MsgReceivedEventHandler MessageReceived; -} \ No newline at end of file + public event NewChatMembersHandler NewChatMembers; + public event ContactSharedHandler ContactShared; +} diff --git a/Botticelli.Framework.Telegram/Http/OutcomeThrottlingDelegatingHandler.cs b/Botticelli.Framework.Telegram/Http/OutcomeThrottlingDelegatingHandler.cs new file mode 100644 index 00000000..270641c5 --- /dev/null +++ b/Botticelli.Framework.Telegram/Http/OutcomeThrottlingDelegatingHandler.cs @@ -0,0 +1,30 @@ +namespace Botticelli.Framework.Telegram.Http; + +public class OutcomeThrottlingDelegatingHandler() : DelegatingHandler(new HttpClientHandler()) +{ + private static readonly TimeSpan Delay = TimeSpan.FromMilliseconds(500); + private static readonly TimeSpan MaxDeviation = TimeSpan.FromMilliseconds(100); + private readonly Random _random = Random.Shared; + private readonly SemaphoreSlim _throttler = new(3, 3); + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(request); + + var randComponent = + TimeSpan.FromMilliseconds(_random.Next((int)-MaxDeviation.TotalMilliseconds, (int)MaxDeviation.TotalMilliseconds)); + var sumDelay = Delay + randComponent; + + await _throttler.WaitAsync(cancellationToken); + await Task.Delay(sumDelay, cancellationToken); + + try + { + return await base.SendAsync(request, cancellationToken); + } + finally + { + _throttler.Release(); + } + } +} \ No newline at end of file diff --git a/Botticelli.Framework.Telegram/Layout/IInlineTelegramLayoutSupplier.cs b/Botticelli.Framework.Telegram/Layout/IInlineTelegramLayoutSupplier.cs index b8c63244..81c7dbd0 100644 --- a/Botticelli.Framework.Telegram/Layout/IInlineTelegramLayoutSupplier.cs +++ b/Botticelli.Framework.Telegram/Layout/IInlineTelegramLayoutSupplier.cs @@ -1,4 +1,4 @@ -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Parsers; using Telegram.Bot.Types.ReplyMarkups; namespace Botticelli.Framework.Telegram.Layout; diff --git a/Botticelli.Framework.Telegram/Layout/IReplyTelegramLayoutSupplier.cs b/Botticelli.Framework.Telegram/Layout/IReplyTelegramLayoutSupplier.cs index ea4a2b4c..08f75299 100644 --- a/Botticelli.Framework.Telegram/Layout/IReplyTelegramLayoutSupplier.cs +++ b/Botticelli.Framework.Telegram/Layout/IReplyTelegramLayoutSupplier.cs @@ -1,4 +1,4 @@ -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Parsers; using Telegram.Bot.Types.ReplyMarkups; namespace Botticelli.Framework.Telegram.Layout; diff --git a/Botticelli.Framework.Telegram/Layout/InlineTelegramLayoutSupplier.cs b/Botticelli.Framework.Telegram/Layout/InlineTelegramLayoutSupplier.cs index e666bacb..fae80e43 100644 --- a/Botticelli.Framework.Telegram/Layout/InlineTelegramLayoutSupplier.cs +++ b/Botticelli.Framework.Telegram/Layout/InlineTelegramLayoutSupplier.cs @@ -1,5 +1,5 @@ -using Botticelli.Framework.Controls.Exceptions; -using Botticelli.Framework.Controls.Layouts; +using Botticelli.Controls.Exceptions; +using Botticelli.Controls.Layouts; using Telegram.Bot.Types.ReplyMarkups; namespace Botticelli.Framework.Telegram.Layout; diff --git a/Botticelli.Framework.Telegram/Layout/ReplyTelegramLayoutSupplier.cs b/Botticelli.Framework.Telegram/Layout/ReplyTelegramLayoutSupplier.cs index 0c5261c9..90d29923 100644 --- a/Botticelli.Framework.Telegram/Layout/ReplyTelegramLayoutSupplier.cs +++ b/Botticelli.Framework.Telegram/Layout/ReplyTelegramLayoutSupplier.cs @@ -1,5 +1,5 @@ -using Botticelli.Framework.Controls.Exceptions; -using Botticelli.Framework.Controls.Layouts; +using Botticelli.Controls.Exceptions; +using Botticelli.Controls.Layouts; using Telegram.Bot.Types.ReplyMarkups; namespace Botticelli.Framework.Telegram.Layout; diff --git a/Botticelli.Framework.Telegram/TelegramBot.cs b/Botticelli.Framework.Telegram/TelegramBot.cs index d71d9c87..38ff9c5b 100644 --- a/Botticelli.Framework.Telegram/TelegramBot.cs +++ b/Botticelli.Framework.Telegram/TelegramBot.cs @@ -34,12 +34,13 @@ public class TelegramBot : BaseBot private readonly ITextTransformer _textTransformer; protected readonly ITelegramBotClient Client; + // ReSharper disable once MemberCanBeProtected.Global public TelegramBot(ITelegramBotClient client, IBotUpdateHandler handler, ILogger logger, - MetricsProcessor metrics, ITextTransformer textTransformer, - IBotDataAccess data) : base(logger, metrics) + IBotDataAccess data, + MetricsProcessor? metrics) : base(logger, metrics) { BotStatusKeeper.IsStarted = false; Client = client; @@ -53,7 +54,9 @@ public TelegramBot(ITelegramBotClient client, public override event MsgSentEventHandler? MessageSent; public override event MsgReceivedEventHandler? MessageReceived; public override event MsgRemovedEventHandler? MessageRemoved; - + public override event ContactSharedEventHandler? ContactShared; + public override event NewChatMembersEventHandler? NewChatMembers; + /// /// Deletes a message /// @@ -61,7 +64,7 @@ public TelegramBot(ITelegramBotClient client, /// /// /// - protected override async Task InnerDeleteMessageAsync(RemoveMessageRequest request, + protected override async Task InnerDeleteMessageAsync(DeleteMessageRequest request, CancellationToken token) { request.NotNull(); @@ -100,17 +103,17 @@ await Client.DeleteMessage(request.ChatId, { MessageUid = request.Uid }; - + MessageRemoved?.Invoke(this, eventArgs); return response; } - protected override Task AdditionalProcessing(SendMessageRequest request, - ISendOptionsBuilder? optionsBuilder, - bool isUpdate, - string chatId, - CancellationToken token) + protected virtual Task AdditionalProcessing(SendMessageRequest request, + ISendOptionsBuilder? optionsBuilder, + bool isUpdate, + string chatId, + CancellationToken token) { return Task.CompletedTask; } @@ -148,11 +151,16 @@ protected override async Task InnerSendMessageAsync InnerSendMessageAsync !request.Message.ChatIdInnerIdLinks.ContainsKey(c)); pairs.AddRange(chatIdOnly.Select(c => (c, string.Empty))); - Logger.LogInformation($"Pairs count: {pairs.Count}"); + Logger.LogInformation("Pairs count: {Count}", pairs.Count); for (var i = 0; i < pairs.Count; i++) { var link = pairs[i]; Message? message = null; - await ProcessText(request, - isUpdate, - token, - retText, - replyMarkup, - link); + await ProcessText(request, + isUpdate, + token, + retText, + replyMarkup, + link); if (request.Message.Poll != null) - message = await ProcessPoll(request, - token, - link, - replyMarkup, - response); + message = await ProcessPoll(request, + token, + link, + replyMarkup, + response); if (request.Message.Contact != null) await ProcessContact(request, @@ -208,9 +216,8 @@ await AdditionalProcessing(request, replyMarkup, response, message); - message.NotNull(); - - AddChatIdInnerIdLink(response, link.chatId, message); + if (message != null) + AddChatIdInnerIdLink(response, link.chatId, message); } response.MessageSentStatus = MessageSentStatus.Ok; @@ -232,12 +239,12 @@ await AdditionalProcessing(request, return response; } - protected virtual async Task ProcessText(SendMessageRequest request, - bool isUpdate, - CancellationToken token, - string retText, - ReplyMarkup? replyMarkup, - (string chatId, string innerId) link) + private async Task ProcessText(SendMessageRequest request, + bool isUpdate, + CancellationToken token, + string retText, + ReplyMarkup? replyMarkup, + (string chatId, string innerId) link) { if (!string.IsNullOrWhiteSpace(retText)) { @@ -278,15 +285,16 @@ await Client.EditMessageText(link.chatId, } } - protected virtual async Task ProcessAttachments(SendMessageRequest request, - CancellationToken token, - (string chatId, string innerId) link, - ReplyMarkup? replyMarkup, - SendMessageResponse response, - Message? message) + private async Task ProcessAttachments(SendMessageRequest request, + CancellationToken token, + (string chatId, string innerId) link, + ReplyMarkup? replyMarkup, + SendMessageResponse response, + Message? message) { request.Message.NotNull(); - request.Message.Attachments.NotNullOrEmpty(); + + if (request.Message.Attachments == null) return message; foreach (var attachment in request.Message .Attachments @@ -377,11 +385,11 @@ await ProcessContact(request, return message; } - protected virtual async Task ProcessPoll(SendMessageRequest request, - CancellationToken token, - (string chatId, string innerId) link, - ReplyMarkup? replyMarkup, - SendMessageResponse response) + protected async Task ProcessPoll(SendMessageRequest request, + CancellationToken token, + (string chatId, string innerId) link, + ReplyMarkup? replyMarkup, + SendMessageResponse response) { Message? message; @@ -393,7 +401,7 @@ await ProcessContact(request, { Poll.PollType.Quiz => PollType.Quiz, Poll.PollType.Regular => PollType.Regular, - _ => throw new ArgumentOutOfRangeException() + _ => throw new ArgumentOutOfRangeException(nameof(Type)) }; message = await Client.SendPoll(link.chatId, @@ -415,7 +423,7 @@ await ProcessContact(request, Id = message.Poll.Id, IsAnonymous = message.Poll.IsAnonymous, Question = message.Poll.Question, - Type = message.Poll.Type.ToLower() == "regular" ? Poll.PollType.Regular : Poll.PollType.Quiz, + Type = message.Poll.Type == PollType.Regular ? Poll.PollType.Regular : Poll.PollType.Quiz, Variants = message.Poll.Options.Select(o => new ValueTuple(o.Text, o.VoterCount)), CorrectAnswerId = message.Poll.CorrectOptionId }; @@ -443,10 +451,10 @@ private static void AddChatIdInnerIdLink(SendMessageResponse response, string ch response.Message.ChatIdInnerIdLinks[chatId].Add(message!.MessageId.ToString()); } - protected virtual async Task ProcessContact(SendMessageRequest request, - SendMessageResponse response, - CancellationToken token, - ReplyMarkup? replyMarkup) + protected async Task ProcessContact(SendMessageRequest request, + SendMessageResponse response, + CancellationToken token, + ReplyMarkup? replyMarkup) { request.Message.NotNull(); request.Message.Contact.NotNull(); @@ -503,9 +511,13 @@ protected override Task InnerStartBotAsync(StartBotRequest req BotStatusKeeper.IsStarted = true; - // Rethrowing an event from BotUpdateHandler + // Rethrowing events from BotUpdateHandler _handler.MessageReceived += (sender, e) => MessageReceived?.Invoke(sender, e); + _handler.ContactShared += (sender, e) + => ContactShared?.Invoke(sender, e); + _handler.NewChatMembers += (sender, e) + => NewChatMembers?.Invoke(sender, e); Client.StartReceiving(_handler, cancellationToken: token); @@ -584,14 +596,11 @@ public override async Task SetBotContext(BotData.Entities.Bot.BotData? context, await StartBot(token); } - else if (currentContext != null) + else if (currentContext != null && Client.BotId <= 0) { - if (Client.BotId == 0) - { - await StopBot(token); - RecreateClient(context.BotKey!); - await StartBot(token); - } + await StopBot(token); + RecreateClient(context.BotKey!); + await StartBot(token); } } } \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Markups/Action.cs b/Botticelli.Framework.Vk/API/Markups/Action.cs deleted file mode 100644 index e8b6af6a..00000000 --- a/Botticelli.Framework.Vk/API/Markups/Action.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Markups; - -public class Action -{ - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("app_id")] - public int AppId { get; set; } - - [JsonPropertyName("owner_id")] - public int OwnerId { get; set; } - - [JsonPropertyName("hash")] - public string Hash { get; set; } - - [JsonPropertyName("payload")] - public string Payload { get; set; } - - [JsonPropertyName("label")] - public string Label { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Markups/VkItem.cs b/Botticelli.Framework.Vk/API/Markups/VkItem.cs deleted file mode 100644 index 62fe3543..00000000 --- a/Botticelli.Framework.Vk/API/Markups/VkItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Markups; - -public class VkItem -{ - [JsonPropertyName("action")] - public Action Action { get; set; } - - [JsonPropertyName("color")] - public string Color { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Markups/VkKeyboardMarkup.cs b/Botticelli.Framework.Vk/API/Markups/VkKeyboardMarkup.cs deleted file mode 100644 index b753f2e3..00000000 --- a/Botticelli.Framework.Vk/API/Markups/VkKeyboardMarkup.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Markups; - -public class VkKeyboardMarkup -{ - [JsonPropertyName("one_time")] - public bool OneTime { get; set; } - - [JsonPropertyName("buttons")] - public IEnumerable> Buttons { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Requests/PollRequest.cs b/Botticelli.Framework.Vk/API/Requests/PollRequest.cs deleted file mode 100644 index b9639824..00000000 --- a/Botticelli.Framework.Vk/API/Requests/PollRequest.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Botticelli.Framework.Vk.Messages.API.Requests; - -public class PollRequest -{ - /// - /// Session key - /// - public string Key { get; set; } - - /// - /// Number of the last event, from which we need to get - /// subsequent events - /// - public long Ts { get; set; } - - /// - /// Time of waiting in seconds - /// - public int Wait { get; set; } = 100; - - /// - /// Additional mode settings - /// - public int? Mode { get; set; } - - /// - /// Long poll API ver - /// - public int Version => 3; -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Requests/VkSendMessageRequest.cs b/Botticelli.Framework.Vk/API/Requests/VkSendMessageRequest.cs deleted file mode 100644 index 7000c06b..00000000 --- a/Botticelli.Framework.Vk/API/Requests/VkSendMessageRequest.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Requests; - -public class VkSendMessageRequest -{ - public VkSendMessageRequest() - { - RandomId = new Random(DateTime.Now.Millisecond).Next(); - } - - [JsonPropertyName("user_id")] - public string UserId { get; set; } - - [JsonPropertyName("peer_id")] - public string PeerId { get; set; } - - [JsonPropertyName("random_id")] - public int RandomId { get; } - - [JsonPropertyName("message")] - public string? Body { get; set; } - - [JsonPropertyName("reply_to")] - public string? ReplyTo { get; set; } - - [JsonPropertyName("lat")] - public decimal? Lat { get; set; } - - [JsonPropertyName("long")] - public decimal? Long { get; set; } - - [JsonPropertyName("access_token")] - public string AccessToken { get; set; } - - [JsonPropertyName("attachment")] - public string? Attachment { get; set; } - - [JsonPropertyName("v")] - public string ApiVersion => "5.131"; -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/AudioMessage.cs b/Botticelli.Framework.Vk/API/Responses/AudioMessage.cs deleted file mode 100644 index b2325431..00000000 --- a/Botticelli.Framework.Vk/API/Responses/AudioMessage.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class AudioMessage -{ - [JsonPropertyName("duration")] - public int Duration { get; set; } - - [JsonPropertyName("id")] - public int Id { get; set; } - - [JsonPropertyName("link_mp3")] - public string LinkMp3 { get; set; } - - [JsonPropertyName("link_ogg")] - public string LinkOgg { get; set; } - - [JsonPropertyName("owner_id")] - public int OwnerId { get; set; } - - [JsonPropertyName("access_key")] - public string AccessKey { get; set; } - - [JsonPropertyName("waveform")] - public List Waveform { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/AudioResponseData.cs b/Botticelli.Framework.Vk/API/Responses/AudioResponseData.cs deleted file mode 100644 index af908ef6..00000000 --- a/Botticelli.Framework.Vk/API/Responses/AudioResponseData.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class AudioResponseData -{ - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("audio_message")] - public AudioMessage AudioMessage { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/Document.cs b/Botticelli.Framework.Vk/API/Responses/Document.cs deleted file mode 100644 index 2c6e0cff..00000000 --- a/Botticelli.Framework.Vk/API/Responses/Document.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class Document -{ - [JsonPropertyName("id")] - public int Id { get; set; } - - [JsonPropertyName("owner_id")] - public int OwnerId { get; set; } - - [JsonPropertyName("title")] - public string Title { get; set; } - - [JsonPropertyName("size")] - public int Size { get; set; } - - [JsonPropertyName("ext")] - public string Ext { get; set; } - - [JsonPropertyName("date")] - public int Date { get; set; } - - [JsonPropertyName("type")] - public int Type { get; set; } - - [JsonPropertyName("url")] - public string Url { get; set; } - - [JsonPropertyName("is_unsafe")] - public int IsUnsafe { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/DocumentResponseData.cs b/Botticelli.Framework.Vk/API/Responses/DocumentResponseData.cs deleted file mode 100644 index 1f3b9020..00000000 --- a/Botticelli.Framework.Vk/API/Responses/DocumentResponseData.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class DocumentResponseData -{ - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("doc")] - public Document Document { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/ErrorResponse.cs b/Botticelli.Framework.Vk/API/Responses/ErrorResponse.cs deleted file mode 100644 index 8d919f15..00000000 --- a/Botticelli.Framework.Vk/API/Responses/ErrorResponse.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -/// -/// Update() response -/// -public class ErrorResponse -{ - /// - /// Offset - /// - [JsonPropertyName("ts")] - public int? Ts { get; set; } - - /// - /// Error code - /// - [JsonPropertyName("failed")] - public int Failed { get; set; } - - /// - /// Min API version - /// - [JsonPropertyName("min_version")] - public int MinVersion { get; set; } - - /// - /// Max API version - /// - [JsonPropertyName("max_version")] - public int MaxVersion { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/GetMessageSessionDataResponse.cs b/Botticelli.Framework.Vk/API/Responses/GetMessageSessionDataResponse.cs deleted file mode 100644 index ca85d3b9..00000000 --- a/Botticelli.Framework.Vk/API/Responses/GetMessageSessionDataResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class GetMessageSessionDataResponse -{ - [JsonPropertyName("response")] - public SessionDataResponse Response { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/GetUploadAddress.cs b/Botticelli.Framework.Vk/API/Responses/GetUploadAddress.cs deleted file mode 100644 index 70b74ed8..00000000 --- a/Botticelli.Framework.Vk/API/Responses/GetUploadAddress.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class GetUploadAddress -{ - [JsonPropertyName("response")] - public GetUploadAddressResponse Response { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/GetUploadAddressResponse.cs b/Botticelli.Framework.Vk/API/Responses/GetUploadAddressResponse.cs deleted file mode 100644 index 3420acb2..00000000 --- a/Botticelli.Framework.Vk/API/Responses/GetUploadAddressResponse.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -// VkSendDocumentResponse myDeserializedClass = JsonSerializer.Deserialize(myJsonResponse); -public class GetUploadAddressResponse -{ - [JsonPropertyName("album_id")] - public int AlbumId { get; set; } - - [JsonPropertyName("upload_url")] - public string UploadUrl { get; set; } - - [JsonPropertyName("user_id")] - public int UserId { get; set; } - - [JsonPropertyName("group_id")] - public int GroupId { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/SendPhotoSize.cs b/Botticelli.Framework.Vk/API/Responses/SendPhotoSize.cs deleted file mode 100644 index 1bcd8e24..00000000 --- a/Botticelli.Framework.Vk/API/Responses/SendPhotoSize.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class SendPhotoSize -{ - [JsonPropertyName("height")] - public int Height { get; set; } - - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("width")] - public int Width { get; set; } - - [JsonPropertyName("url")] - public string Url { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/SessionDataResponse.cs b/Botticelli.Framework.Vk/API/Responses/SessionDataResponse.cs deleted file mode 100644 index 2dcafdf1..00000000 --- a/Botticelli.Framework.Vk/API/Responses/SessionDataResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class SessionDataResponse -{ - [JsonPropertyName("server")] - public string Server { get; set; } - - [JsonPropertyName("key")] - public string Key { get; set; } - - [JsonPropertyName("ts")] - public string Ts { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/UpdateEvent.cs b/Botticelli.Framework.Vk/API/Responses/UpdateEvent.cs deleted file mode 100644 index 75b93ab2..00000000 --- a/Botticelli.Framework.Vk/API/Responses/UpdateEvent.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -/// -/// Particular events -/// -public class UpdateEvent -{ - /// - /// Event type - /// - [JsonPropertyName("type")] - public string Type { get; set; } - - /// - /// Id of an event - /// - [JsonPropertyName("event_id")] - public string EventId { get; set; } - - /// - /// Id of a group - /// - [JsonPropertyName("group_id")] - public long GroupId { get; set; } - - /// - /// Inner object - /// - [JsonPropertyName("object")] - public Dictionary Object { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/UpdatesResponse.cs b/Botticelli.Framework.Vk/API/Responses/UpdatesResponse.cs deleted file mode 100644 index c9df6bf7..00000000 --- a/Botticelli.Framework.Vk/API/Responses/UpdatesResponse.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -/// -/// Update() response -/// -public class UpdatesResponse -{ - /// - /// Offset - /// - [JsonPropertyName("ts")] - public string Ts { get; set; } - - /// - /// Updated objects - /// - [JsonPropertyName("updates")] - public List Updates { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/UploadDocResult.cs b/Botticelli.Framework.Vk/API/Responses/UploadDocResult.cs deleted file mode 100644 index 5905555d..00000000 --- a/Botticelli.Framework.Vk/API/Responses/UploadDocResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class UploadDocResult -{ - [JsonPropertyName("server")] - public int Server { get; set; } - - [JsonPropertyName("file")] - public string File { get; set; } - - [JsonPropertyName("hash")] - public string Hash { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/UploadPhotoResponse.cs b/Botticelli.Framework.Vk/API/Responses/UploadPhotoResponse.cs deleted file mode 100644 index b96cbf5c..00000000 --- a/Botticelli.Framework.Vk/API/Responses/UploadPhotoResponse.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class UploadPhotoResponse -{ - [JsonPropertyName("album_id")] - public int AlbumId { get; set; } - - [JsonPropertyName("date")] - public int Date { get; set; } - - [JsonPropertyName("id")] - public int Id { get; set; } - - [JsonPropertyName("owner_id")] - public int OwnerId { get; set; } - - [JsonPropertyName("access_key")] - public string AccessKey { get; set; } - - [JsonPropertyName("sizes")] - public List Sizes { get; set; } - - [JsonPropertyName("text")] - public string Text { get; set; } - - [JsonPropertyName("user_id")] - public int UserId { get; set; } - - [JsonPropertyName("has_tags")] - public bool HasTags { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/UploadPhotoResult.cs b/Botticelli.Framework.Vk/API/Responses/UploadPhotoResult.cs deleted file mode 100644 index ffaab75e..00000000 --- a/Botticelli.Framework.Vk/API/Responses/UploadPhotoResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class UploadPhotoResult -{ - [JsonPropertyName("server")] - public int Server { get; set; } - - [JsonPropertyName("photo")] - public string Photo { get; set; } - - [JsonPropertyName("hash")] - public string Hash { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/UploadPhotoSize.cs b/Botticelli.Framework.Vk/API/Responses/UploadPhotoSize.cs deleted file mode 100644 index dd69cd18..00000000 --- a/Botticelli.Framework.Vk/API/Responses/UploadPhotoSize.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class UploadPhotoSize -{ - [JsonPropertyName("height")] - public int Height { get; set; } - - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("width")] - public int Width { get; set; } - - [JsonPropertyName("url")] - public string Url { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/UploadVideoResult.cs b/Botticelli.Framework.Vk/API/Responses/UploadVideoResult.cs deleted file mode 100644 index b92104f1..00000000 --- a/Botticelli.Framework.Vk/API/Responses/UploadVideoResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class UploadVideoResult -{ - [JsonPropertyName("server")] - public int Server { get; set; } - - [JsonPropertyName("video")] - public string Video { get; set; } - - [JsonPropertyName("hash")] - public string Hash { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkErrorEventArgs.cs b/Botticelli.Framework.Vk/API/Responses/VkErrorEventArgs.cs deleted file mode 100644 index 19485f64..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkErrorEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkErrorEventArgs -{ - public VkErrorEventArgs(ErrorResponse response) - { - Response = response; - } - - public ErrorResponse Response { get; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkSendAudioResponse.cs b/Botticelli.Framework.Vk/API/Responses/VkSendAudioResponse.cs deleted file mode 100644 index da912ac5..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkSendAudioResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkSendAudioResponse -{ - [JsonPropertyName("response")] - public AudioResponseData AudioResponseData { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkSendDocumentResponse.cs b/Botticelli.Framework.Vk/API/Responses/VkSendDocumentResponse.cs deleted file mode 100644 index 581a110e..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkSendDocumentResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkSendDocumentResponse -{ - [JsonPropertyName("response")] - public DocumentResponseData DocumentResponseData { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkSendPhotoPartialResponse.cs b/Botticelli.Framework.Vk/API/Responses/VkSendPhotoPartialResponse.cs deleted file mode 100644 index 828e0a16..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkSendPhotoPartialResponse.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkSendPhotoPartialResponse -{ - [JsonPropertyName("album_id")] - public int AlbumId { get; set; } - - [JsonPropertyName("date")] - public int Date { get; set; } - - [JsonPropertyName("id")] - public int Id { get; set; } - - [JsonPropertyName("owner_id")] - public int OwnerId { get; set; } - - [JsonPropertyName("access_key")] - public string AccessKey { get; set; } - - [JsonPropertyName("sizes")] - public List Sizes { get; set; } - - [JsonPropertyName("text")] - public string Text { get; set; } - - [JsonPropertyName("user_id")] - public int UserId { get; set; } - - [JsonPropertyName("has_tags")] - public bool HasTags { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkSendPhotoResponse.cs b/Botticelli.Framework.Vk/API/Responses/VkSendPhotoResponse.cs deleted file mode 100644 index 3a2a0c34..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkSendPhotoResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkSendPhotoResponse -{ - [JsonPropertyName("response")] - public List? Response { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkSendVideoResponse.cs b/Botticelli.Framework.Vk/API/Responses/VkSendVideoResponse.cs deleted file mode 100644 index 76f57cc5..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkSendVideoResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkSendVideoResponse -{ - [JsonPropertyName("response")] - public VkSendVideoResponseData Response { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkSendVideoResponseData.cs b/Botticelli.Framework.Vk/API/Responses/VkSendVideoResponseData.cs deleted file mode 100644 index 5a450a73..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkSendVideoResponseData.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkSendVideoResponseData -{ - [JsonPropertyName("access_key")] - public string AccessKey { get; set; } - - [JsonPropertyName("description")] - public string Description { get; set; } - - [JsonPropertyName("owner_id")] - public int OwnerId { get; set; } - - [JsonPropertyName("title")] - public string Title { get; set; } - - [JsonPropertyName("upload_url")] - public string UploadUrl { get; set; } - - [JsonPropertyName("video_id")] - public int VideoId { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Responses/VkUpdatesEventArgs.cs b/Botticelli.Framework.Vk/API/Responses/VkUpdatesEventArgs.cs deleted file mode 100644 index 43585ecc..00000000 --- a/Botticelli.Framework.Vk/API/Responses/VkUpdatesEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Botticelli.Framework.Vk.Messages.API.Responses; - -public class VkUpdatesEventArgs -{ - public VkUpdatesEventArgs(UpdatesResponse response) - { - Response = response; - } - - public UpdatesResponse Response { get; } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/API/Utils/ApiUtils.cs b/Botticelli.Framework.Vk/API/Utils/ApiUtils.cs deleted file mode 100644 index 341d958c..00000000 --- a/Botticelli.Framework.Vk/API/Utils/ApiUtils.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Reflection; -using System.Text.Json.Serialization; -using Botticelli.Shared.Utils; -using Flurl; - -namespace Botticelli.Framework.Vk.Messages.API.Utils; - -public static class ApiUtils -{ - public static Uri GetMethodUri(string baseAddress, - string method, - object? methodParams = null, - bool snakeCase = true) - { - if (methodParams == null) return new Uri(Url.Combine(baseAddress, "method", method)); - - if (!snakeCase) return new Uri(Url.Combine(baseAddress, "method", method).SetQueryParams(methodParams)); - - var snaked = new Dictionary(); - var props = methodParams.GetType().GetProperties(); - - - foreach (var prop in props) - { - var value = prop.GetValue(methodParams) ?? string.Empty; - - snaked[prop.Name.ToSnakeCase()] = value; - } - - return new Uri(Url.Combine(baseAddress, "method", method).SetQueryParams(snaked)); - } - - public static MultipartFormDataContent GetMethodMultipartFormContent(object? methodParams = null, - bool snakeCase = true) - { - var props = methodParams.GetType().GetProperties(); - var content = new MultipartFormDataContent(); - - foreach (var prop in props) - { - var value = prop.GetValue(methodParams) as string ?? string.Empty; - - content.Add(new StringContent(value), snakeCase ? prop.Name.ToSnakeCase() : prop.Name); - } - - return content; - } - - public static Uri GetMethodUriWithJson(string baseAddress, string method, object? methodParams = null) - { - var snaked = new Dictionary(); - var props = methodParams.GetType().GetProperties(); - - - foreach (var prop in props) - { - var value = prop.GetValue(methodParams) ?? string.Empty; - - var jpName = prop.GetCustomAttribute(); - - snaked[jpName?.Name ?? prop.Name] = value; - } - - return new Uri(Url.Combine(baseAddress, "method", method).SetQueryParams(snaked)); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Botticelli.Framework.Vk.Messages.csproj b/Botticelli.Framework.Vk/Botticelli.Framework.Vk.Messages.csproj deleted file mode 100644 index 4f8716e0..00000000 --- a/Botticelli.Framework.Vk/Botticelli.Framework.Vk.Messages.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - net8.0 - enable - enable - 0.7.0 - Botticelli.Framework.Vk.Messages - BotticelliBots - new_logo_compact.png - Botticelli VK messenger integration - BotticelliBots - https://botticellibots.com - https://github.com/devgopher/botticelli - telegram, bots, botticelli, vk, facebook, wechat, whatsapp - true - - - - - True - - new_logo_compact.png - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Builders/LongPollMessagesProviderBuilder.cs b/Botticelli.Framework.Vk/Builders/LongPollMessagesProviderBuilder.cs deleted file mode 100644 index bba1f63a..00000000 --- a/Botticelli.Framework.Vk/Builders/LongPollMessagesProviderBuilder.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Botticelli.Framework.Options; -using Botticelli.Framework.Vk.Messages.Options; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages.Builders; - -/// -/// Builds a Long poll message provider|receiver -/// -public class LongPollMessagesProviderBuilder -{ - private readonly BotSettingsBuilder _settingsBuilder; - private IHttpClientFactory _httpClientFactory; - private ILogger _logger; - - private LongPollMessagesProviderBuilder(BotSettingsBuilder settingsBuilder) - { - _settingsBuilder = settingsBuilder; - } - - public static LongPollMessagesProviderBuilder Instance(BotSettingsBuilder settingsBuilder) - { - return new LongPollMessagesProviderBuilder(settingsBuilder); - } - - public LongPollMessagesProviderBuilder AddLogger(ILogger logger) - { - _logger = logger; - - return this; - } - - public LongPollMessagesProviderBuilder AddHttpClientFactory(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - - return this; - } - - public LongPollMessagesProvider Build() - { - return new LongPollMessagesProvider(_settingsBuilder.Build(), _httpClientFactory, _logger); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Builders/MessagePublisherBuilder.cs b/Botticelli.Framework.Vk/Builders/MessagePublisherBuilder.cs deleted file mode 100644 index c552870c..00000000 --- a/Botticelli.Framework.Vk/Builders/MessagePublisherBuilder.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Botticelli.Framework.Options; -using Botticelli.Framework.Vk.Messages.Options; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages.Builders; - -/// -/// Builds a MessagePublisher -/// -public class MessagePublisherBuilder -{ - private readonly BotSettingsBuilder _settingsBuilder; - private IHttpClientFactory _httpClientFactory; - private ILogger _logger; - - private MessagePublisherBuilder(BotSettingsBuilder settingsBuilder) - { - _settingsBuilder = settingsBuilder; - } - - public static MessagePublisherBuilder Instance(BotSettingsBuilder settingsBuilder) - { - return new MessagePublisherBuilder(settingsBuilder); - } - - public MessagePublisherBuilder AddLogger(ILogger logger) - { - _logger = logger; - - return this; - } - - public MessagePublisherBuilder AddHttpClientFactory(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - - return this; - } - - public MessagePublisher Build() - { - return new MessagePublisher(_httpClientFactory, _logger); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Builders/VkBotBuilder.cs b/Botticelli.Framework.Vk/Builders/VkBotBuilder.cs deleted file mode 100644 index b6db5559..00000000 --- a/Botticelli.Framework.Vk/Builders/VkBotBuilder.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Botticelli.Bot.Data; -using Botticelli.Bot.Data.Repositories; -using Botticelli.Bot.Data.Settings; -using Botticelli.Bot.Utils; -using Botticelli.Bot.Utils.TextUtils; -using Botticelli.Client.Analytics; -using Botticelli.Client.Analytics.Settings; -using Botticelli.Framework.Builders; -using Botticelli.Framework.Extensions; -using Botticelli.Framework.Options; -using Botticelli.Framework.Security; -using Botticelli.Framework.Services; -using Botticelli.Framework.Vk.Messages.Handlers; -using Botticelli.Framework.Vk.Messages.HostedService; -using Botticelli.Framework.Vk.Messages.Options; -using Botticelli.Framework.Vk.Messages.Utils; -using Botticelli.Interfaces; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages.Builders; - -public class VkBotBuilder : BotBuilder -{ - private LongPollMessagesProvider? _longPollMessagesProvider; - private LongPollMessagesProviderBuilder? _longPollMessagesProviderBuilder; - private MessagePublisher? _messagePublisher; - private MessagePublisherBuilder? _messagePublisherBuilder; - private VkStorageUploader? _vkStorageUploader; - private VkStorageUploaderBuilder? _vkStorageUploaderBuilder; - - private VkBotSettings? BotSettings { get; set; } - - protected override VkBot InnerBuild() - { - Services!.AddHttpClient() - .AddServerCertificates(BotSettings); - Services!.AddHostedService(); - Services!.AddHttpClient() - .AddServerCertificates(BotSettings); - Services!.AddHttpClient>() - .AddServerCertificates(BotSettings); - Services!.AddHostedService(); - Services!.AddHostedService>>(); - - Services!.AddHostedService(); - var botId = BotDataUtils.GetBotId(); - - if (botId == null) throw new InvalidDataException($"{nameof(botId)} shouldn't be null!"); - - #region Metrics - - var metricsPublisher = new MetricsPublisher(AnalyticsClientSettingsBuilder.Build()); - var metricsProcessor = new MetricsProcessor(metricsPublisher); - Services!.AddSingleton(metricsPublisher); - Services!.AddSingleton(metricsProcessor); - - #endregion - - #region Data - - Services!.AddDbContext(o => - o.UseSqlite($"Data source={BotDataAccessSettingsBuilder.Build().ConnectionString}")); - Services!.AddScoped(); - - #endregion - - #region TextTransformer - - Services!.AddTransient(); - - #endregion - - _longPollMessagesProvider = _longPollMessagesProviderBuilder.Build(); - _messagePublisher = _messagePublisherBuilder.Build(); - _vkStorageUploader = _vkStorageUploaderBuilder.Build(); - - Services!.AddBotticelliFramework() - .AddSingleton(); - - var sp = Services!.BuildServiceProvider(); - - return new VkBot(_longPollMessagesProvider, - _messagePublisher, - _vkStorageUploader, - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService>()); - } - - public override VkBotBuilder AddBotSettings(BotSettingsBuilder settingsBuilder) - { - BotSettings = settingsBuilder.Build() as VkBotSettings ?? throw new InvalidOperationException(); - - return this; - } - - public VkBotBuilder AddClient(LongPollMessagesProviderBuilder builder) - { - _longPollMessagesProviderBuilder = builder; - - return this; - } - - public static VkBotBuilder Instance(IServiceCollection services, - ServerSettingsBuilder serverSettingsBuilder, - BotSettingsBuilder settingsBuilder, - DataAccessSettingsBuilder dataAccessSettingsBuilder, - AnalyticsClientSettingsBuilder analyticsClientSettingsBuilder) - { - return new VkBotBuilder() - .AddServices(services) - .AddServerSettings(serverSettingsBuilder) - .AddAnalyticsSettings(analyticsClientSettingsBuilder) - .AddBotDataAccessSettings(dataAccessSettingsBuilder) - .AddBotSettings(settingsBuilder); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Builders/VkStorageUploaderBuilder.cs b/Botticelli.Framework.Vk/Builders/VkStorageUploaderBuilder.cs deleted file mode 100644 index 204a3658..00000000 --- a/Botticelli.Framework.Vk/Builders/VkStorageUploaderBuilder.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Botticelli.Audio; -using Botticelli.Framework.Options; -using Botticelli.Framework.Vk.Messages.Options; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages.Builders; - -/// -/// Builder for VK media uploader -/// -public class VkStorageUploaderBuilder -{ - private IConvertor _audioConvertor; - private IHttpClientFactory _httpClientFactory; - private ILogger _logger; - - private VkStorageUploaderBuilder() - { - } - - public static VkStorageUploaderBuilder Instance(BotSettingsBuilder settingsBuilder) - { - return new VkStorageUploaderBuilder(); - } - - public VkStorageUploaderBuilder AddLogger(ILogger logger) - { - _logger = logger; - - return this; - } - - public VkStorageUploaderBuilder AddHttpClientFactory(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - - return this; - } - - public VkStorageUploaderBuilder AddAudioConvertor(IConvertor audioConvertor) - { - _audioConvertor = audioConvertor; - - return this; - } - - public VkStorageUploader Build() - { - return new VkStorageUploader(_httpClientFactory, _audioConvertor, _logger); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Framework.Vk/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index a7ed31ea..00000000 --- a/Botticelli.Framework.Vk/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Configuration; -using Botticelli.Bot.Data.Settings; -using Botticelli.Client.Analytics.Settings; -using Botticelli.Framework.Controls.Parsers; -using Botticelli.Framework.Options; -using Botticelli.Framework.Vk.Messages.API.Markups; -using Botticelli.Framework.Vk.Messages.Builders; -using Botticelli.Framework.Vk.Messages.Layout; -using Botticelli.Framework.Vk.Messages.Options; -using Botticelli.Interfaces; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Botticelli.Framework.Vk.Messages.Extensions; - -public static class ServiceCollectionExtensions -{ - private static readonly BotSettingsBuilder SettingsBuilder = new(); - private static readonly ServerSettingsBuilder ServerSettingsBuilder = new(); - - private static readonly AnalyticsClientSettingsBuilder AnalyticsClientOptionsBuilder = - new(); - - private static readonly DataAccessSettingsBuilder DataAccessSettingsBuilder = new(); - - public static IServiceCollection AddVkBot(this IServiceCollection services, IConfiguration configuration) - { - var vkBotSettings = configuration - .GetSection(VkBotSettings.Section) - .Get() ?? - throw new ConfigurationErrorsException($"Can't load configuration for {nameof(VkBotSettings)}!"); - - var analyticsClientSettings = configuration - .GetSection(AnalyticsClientSettings.Section) - .Get() ?? - throw new ConfigurationErrorsException($"Can't load configuration for {nameof(AnalyticsClientSettings)}!"); - - var serverSettings = configuration - .GetSection(ServerSettings.Section) - .Get() ?? - throw new ConfigurationErrorsException($"Can't load configuration for {nameof(ServerSettings)}!"); - - var dataAccessSettings = configuration - .GetSection(DataAccessSettings.Section) - .Get() ?? - throw new ConfigurationErrorsException($"Can't load configuration for {nameof(DataAccessSettings)}!"); - ; - - return services.AddVkBot(vkBotSettings, - analyticsClientSettings, - serverSettings, - dataAccessSettings); - } - - public static IServiceCollection AddVkBot(this IServiceCollection services, - VkBotSettings botSettings, - AnalyticsClientSettings analyticsClientSettings, - ServerSettings serverSettings, - DataAccessSettings dataAccessSettings) - { - return services.AddVkBot(o => o.Set(botSettings), - o => o.Set(analyticsClientSettings), - o => o.Set(serverSettings), - o => o.Set(dataAccessSettings)); - } - - /// - /// Adds a Vk bot - /// - /// - /// - /// - /// - /// - /// - public static IServiceCollection AddVkBot(this IServiceCollection services, - Action> optionsBuilderFunc, - Action> analyticsOptionsBuilderFunc, - Action> serverSettingsBuilderFunc, - Action> dataAccessSettingsBuilderFunc) - { - optionsBuilderFunc(SettingsBuilder); - serverSettingsBuilderFunc(ServerSettingsBuilder); - analyticsOptionsBuilderFunc(AnalyticsClientOptionsBuilder); - dataAccessSettingsBuilderFunc(DataAccessSettingsBuilder); - - var clientBuilder = LongPollMessagesProviderBuilder.Instance(SettingsBuilder); - - var botBuilder = VkBotBuilder.Instance(services, - ServerSettingsBuilder, - SettingsBuilder, - DataAccessSettingsBuilder, - AnalyticsClientOptionsBuilder) - .AddClient(clientBuilder); - var bot = botBuilder.Build(); - - return services.AddSingleton>(bot) - .AddSingleton(bot); - } - - public static IServiceCollection AddVkLayoutsSupport(this IServiceCollection services) - { - return services.AddSingleton() - .AddSingleton, VkLayoutSupplier>() - .AddSingleton, - LayoutLoader, VkKeyboardMarkup>>(); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Handlers/BotUpdateHandler.cs b/Botticelli.Framework.Vk/Handlers/BotUpdateHandler.cs deleted file mode 100644 index a5b2e241..00000000 --- a/Botticelli.Framework.Vk/Handlers/BotUpdateHandler.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Botticelli.Framework.Commands.Processors; -using Botticelli.Framework.Vk.Messages.API.Responses; -using Botticelli.Shared.Utils; -using Botticelli.Shared.ValueObjects; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages.Handlers; - -public class BotUpdateHandler : IBotUpdateHandler -{ - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - - public BotUpdateHandler(ILogger logger, IServiceProvider serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } - - public async Task HandleUpdateAsync(List update, CancellationToken cancellationToken) - { - _logger.LogDebug($"{nameof(HandleUpdateAsync)}() started..."); - - var botMessages = update? - .Where(x => x.Type == "message_new") - .ToList(); - - var messagesText = botMessages?.Select(bm => - new - { - eventId = bm.EventId, - message = bm.Object["message"] - }); - - foreach (var botMessage in messagesText.EmptyIfNull()) - try - { - var eventId = botMessage.eventId; - var peerId = botMessage.message["peer_id"]?.ToString(); - var text = botMessage.message["text"]?.ToString(); - var fromId = botMessage.message["from_id"]?.ToString(); - - var botticelliMessage = new Message(eventId) - { - ChatIds = - [ - peerId.EmptyIfNull(), - fromId.EmptyIfNull() - ], - Subject = string.Empty, - Body = text, - Attachments = new List(5), - From = new User - { - Id = fromId - }, - ForwardedFrom = null, - Location = null! - // LastModifiedAt = botMessage. - }; - - await Process(botticelliMessage, cancellationToken); - } - catch (Exception ex) - { - _logger.LogError(ex, ex.Message); - } - - _logger.LogDebug($"{nameof(HandleUpdateAsync)}() finished..."); - } - - public event IBotUpdateHandler.MsgReceivedEventHandler? MessageReceived; - - /// - /// Processes requests - /// - /// - /// - protected Task Process(Message message, CancellationToken token) - { - _logger.LogDebug($"{nameof(Process)}({message.Uid}) started..."); - - if (token is {CanBeCanceled: true, IsCancellationRequested: true}) return Task.CompletedTask; - - var clientNonChainedTasks = _serviceProvider.GetServices() - .Where(p => !p.GetType().IsAssignableTo(typeof(ICommandChainProcessor))) - .Select(p => p.ProcessAsync(message, token)); - - var clientChainedTasks = _serviceProvider.GetServices() - .Select(p => p.ProcessAsync(message, token)); - - Task.WaitAll(clientNonChainedTasks.Concat(clientChainedTasks).ToArray(), token); - - _logger.LogDebug($"{nameof(Process)}({message.Uid}) finished..."); - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Handlers/IBotUpdateHandler.cs b/Botticelli.Framework.Vk/Handlers/IBotUpdateHandler.cs deleted file mode 100644 index 9934ffb8..00000000 --- a/Botticelli.Framework.Vk/Handlers/IBotUpdateHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Botticelli.Framework.Events; -using Botticelli.Framework.Vk.Messages.API.Responses; - -namespace Botticelli.Framework.Vk.Messages.Handlers; - -public interface IBotUpdateHandler -{ - public delegate void MsgReceivedEventHandler(object sender, MessageReceivedBotEventArgs e); - - public Task HandleUpdateAsync(List update, CancellationToken cancellationToken); - - public event MsgReceivedEventHandler MessageReceived; -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/HostedService/VkBotHostedService.cs b/Botticelli.Framework.Vk/HostedService/VkBotHostedService.cs deleted file mode 100644 index 67429b3f..00000000 --- a/Botticelli.Framework.Vk/HostedService/VkBotHostedService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Botticelli.Interfaces; -using Botticelli.Shared.API.Admin.Requests; -using Microsoft.Extensions.Hosting; - -namespace Botticelli.Framework.Vk.Messages.HostedService; - -public class VkBotHostedService : IHostedService -{ - private readonly IBot _bot; - - public VkBotHostedService(IBot bot) - { - _bot = bot; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - await _bot.StartBotAsync(StartBotRequest.GetInstance(), CancellationToken.None); - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - await _bot.StopBotAsync(StopBotRequest.GetInstance(), CancellationToken.None); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Layout/IVkLayoutSupplier.cs b/Botticelli.Framework.Vk/Layout/IVkLayoutSupplier.cs deleted file mode 100644 index 20b08f50..00000000 --- a/Botticelli.Framework.Vk/Layout/IVkLayoutSupplier.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Botticelli.Framework.Controls.Parsers; -using Botticelli.Framework.Vk.Messages.API.Markups; - -namespace Botticelli.Framework.Vk.Messages.Layout; - -public interface IVkLayoutSupplier : ILayoutSupplier -{ -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Layout/VkLayoutSupplier.cs b/Botticelli.Framework.Vk/Layout/VkLayoutSupplier.cs deleted file mode 100644 index 4cdf4837..00000000 --- a/Botticelli.Framework.Vk/Layout/VkLayoutSupplier.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Botticelli.Framework.Controls.BasicControls; -using Botticelli.Framework.Controls.Exceptions; -using Botticelli.Framework.Controls.Extensions; -using Botticelli.Framework.Controls.Layouts; -using Botticelli.Framework.Vk.Messages.API.Markups; -using Botticelli.Shared.Utils; -using Action = Botticelli.Framework.Vk.Messages.API.Markups.Action; - -namespace Botticelli.Framework.Vk.Messages.Layout; - -public class VkLayoutSupplier : IVkLayoutSupplier -{ - public VkKeyboardMarkup GetMarkup(ILayout layout) - { - if (layout == null) throw new LayoutException("Layout = null!"); - - layout.Rows.NotNull(); - - var buttons = new List>(10); - - foreach (var layoutRow in layout.Rows.EmptyIfNull()) - { - var keyboardElement = new List(); - - keyboardElement.AddRange(layoutRow.Items.Where(i => i.Control != null) - .Select(item => - { - item.Control.NotNull(); - item.Control.Content.NotNull(); - item.Control.MessengerSpecificParams.NotNull(); - - var controlParams = item.Control.MessengerSpecificParams.ContainsKey("VK") ? - item.Control?.MessengerSpecificParams["VK"] : - new Dictionary(); - - controlParams.NotNull(); - - var action = new Action - { - Type = item.Control is TextButton ? "text" : "button", - Payload = $"{{\"button\": \"{layout.Rows.IndexOf(layoutRow)}\"}}", - Label = item.Control!.Content, - AppId = controlParams.ReturnValueOrDefault("AppId"), - OwnerId = controlParams.ReturnValueOrDefault("OwnerId"), - Hash = controlParams.ReturnValueOrDefault("Hash") - }; - - return new VkItem - { - Action = action, - Color = controlParams.ReturnValueOrDefault("Color") - }; - })); - - buttons.Add(keyboardElement); - } - - var markup = new VkKeyboardMarkup - { - OneTime = true, - Buttons = buttons - }; - - return markup; - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/LongPollMessagesProvider.cs b/Botticelli.Framework.Vk/LongPollMessagesProvider.cs deleted file mode 100644 index fa0dc8a7..00000000 --- a/Botticelli.Framework.Vk/LongPollMessagesProvider.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Text.Json; -using Botticelli.Framework.Exceptions; -using Botticelli.Framework.Vk.Messages.API.Responses; -using Botticelli.Framework.Vk.Messages.API.Utils; -using Botticelli.Framework.Vk.Messages.Options; -using Flurl; -using Flurl.Http; -using Microsoft.Extensions.Logging; -using Polly; - -namespace Botticelli.Framework.Vk.Messages; - -/// -/// Long poll method provider -/// https://dev.vk.com/ru/api/bots/getting-started -/// -public class LongPollMessagesProvider : IDisposable -{ - public delegate void GotError(VkErrorEventArgs args, CancellationToken token); - - public delegate void GotUpdates(VkUpdatesEventArgs args, CancellationToken token); - - private readonly int? _groupId = 0; - - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; - private readonly VkBotSettings _settings; - private readonly object _syncObj = new(); - private readonly CancellationTokenSource _tokenSource; - private string _apiKey; - private HttpClient _client; - private bool _isStarted; - private string _key; - private int? _lastTs = 0; - private string _server; - - public LongPollMessagesProvider(VkBotSettings settings, - IHttpClientFactory httpClientFactory, - ILogger logger) - { - _settings = settings; - _httpClientFactory = httpClientFactory; - _logger = logger; - _tokenSource = new CancellationTokenSource(); - _groupId = settings.GroupId; - } - - private string ApiVersion => "5.199"; - - public void Dispose() - { - var stopTask = Stop(); - stopTask.Wait(5000); - _client?.Dispose(); - } - - public event GotUpdates OnUpdates; - public event GotError OnError; - - - public void SetApiKey(string key) - { - _apiKey = key; - } - - public async Task Start(CancellationToken token) - { - try - { - lock (_syncObj) - { - if (_isStarted && - !string.IsNullOrWhiteSpace(_key) && - !string.IsNullOrWhiteSpace(_server) && - !string.IsNullOrWhiteSpace(_apiKey)) - return; - } - - _client = _httpClientFactory.CreateClient(); - - // 1. Get Session - await GetSessionData(); - - // 2. Start polling - if (string.IsNullOrWhiteSpace(_key) || string.IsNullOrWhiteSpace(_server)) - { - _logger.LogError($"{nameof(_key)} or {nameof(_server)} are null or empty!"); - - return; - } - - - int[] codesForRetry = [408, 500, 502, 503, 504]; - - var updatePolicy = Policy - .Handle(ex => - { - _logger.LogError(ex, $"Long polling error! session: {_key}, server: {_server}"); - - return codesForRetry.Contains(ex.Call.Response.StatusCode); - }) - .WaitAndRetryAsync(3, n => n * TimeSpan.FromMilliseconds(_settings.PollIntervalMs)); - - - var repeatPolicy = Policy.HandleResult(r => true) - .WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(_settings.PollIntervalMs)); - var pollingTask = repeatPolicy.WrapAsync(updatePolicy) - .ExecuteAsync(async () => - { - try - { - var updatesResponse = await $"{_server}".SetQueryParams(new - { - act = "a_check", - key = _key, - wait = 90, - ts = _lastTs, - mode = 2, - v = ApiVersion - }) - .GetStringAsync(cancellationToken: _tokenSource.Token); - - var updates = JsonSerializer.Deserialize(updatesResponse); - - - if (updates?.Updates == null) - { - var error = JsonSerializer.Deserialize(updatesResponse); - - if (error == null) return null; - - _lastTs = error.Ts ?? _lastTs; - OnError?.Invoke(new VkErrorEventArgs(error), token); - - return null; - } - - _lastTs = int.Parse(updates?.Ts ?? "0"); - - if (updates?.Updates != null) OnUpdates?.Invoke(new VkUpdatesEventArgs(updates), token); - - return updates; - } - catch (Exception ex) - { - _logger.LogError(ex, $"Long polling error: {ex.Message}"); - } - - return null; - }); - - _isStarted = true; - await pollingTask; - } - catch (Exception ex) when (ex is not BotException) - { - _logger.LogError(ex, $"Can't start a {nameof(LongPollMessagesProvider)}!"); - } - } - - private async Task GetSessionData() - { - var request = new HttpRequestMessage(HttpMethod.Get, - ApiUtils.GetMethodUri("https://api.vk.com", - "groups.getLongPollServer", - new - { - access_token = _apiKey, - group_id = _groupId, - v = ApiVersion - })); - var response = await _client.SendAsync(request, _tokenSource.Token); - var resultString = await response.Content.ReadAsStringAsync(); - - var result = JsonSerializer.Deserialize(resultString); - - _server = result?.Response?.Server; - _key = result?.Response?.Key; - _lastTs = int.Parse(result?.Response?.Ts ?? "0"); - } - - public async Task Stop() - { - _client?.CancelPendingRequests(); - _tokenSource.Cancel(false); - _key = string.Empty; - _server = string.Empty; - _isStarted = false; - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/MessagePublisher.cs b/Botticelli.Framework.Vk/MessagePublisher.cs deleted file mode 100644 index 290e740a..00000000 --- a/Botticelli.Framework.Vk/MessagePublisher.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Net; -using Botticelli.Framework.Vk.Messages.API.Requests; -using Botticelli.Framework.Vk.Messages.API.Utils; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages; - -public class MessagePublisher -{ - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; - private string _apiKey; - - public MessagePublisher(IHttpClientFactory httpClientFactory, ILogger logger) - { - _httpClientFactory = httpClientFactory; - _logger = logger; - } - - public void SetApiKey(string key) - { - _apiKey = key; - } - - - public async Task SendAsync(VkSendMessageRequest vkMessageRequest, CancellationToken token) - { - try - { - vkMessageRequest.AccessToken = _apiKey; - using var httpClient = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Get, - ApiUtils.GetMethodUriWithJson("https://api.vk.com", - "messages.send", - vkMessageRequest)); - - var response = await httpClient.SendAsync(request, token); - if (response.StatusCode != HttpStatusCode.OK) _logger.LogError($"Error sending a message: {response.StatusCode} {response.ReasonPhrase}!"); - - var responseContent = await response.Content.ReadAsStringAsync(token); - - if (string.IsNullOrEmpty(responseContent)) _logger.LogError($"Error sending a message {responseContent}"); - } - catch (HttpRequestException ex) - { - _logger.LogError(ex, "Error sending a message!"); - } - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Options/VkBotSettings.cs b/Botticelli.Framework.Vk/Options/VkBotSettings.cs deleted file mode 100644 index 346e7e29..00000000 --- a/Botticelli.Framework.Vk/Options/VkBotSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Botticelli.Framework.Options; - -namespace Botticelli.Framework.Vk.Messages.Options; - -/// -public class VkBotSettings : BotSettings -{ - public int PollIntervalMs { get; set; } = 500; - public int GroupId { get; set; } - public static string Section => "VkBot"; -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/Utils/VkTextTransformer.cs b/Botticelli.Framework.Vk/Utils/VkTextTransformer.cs deleted file mode 100644 index 3a279211..00000000 --- a/Botticelli.Framework.Vk/Utils/VkTextTransformer.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text; -using Botticelli.Bot.Utils.TextUtils; - -namespace Botticelli.Framework.Vk.Messages.Utils; - -public class VkTextTransformer : ITextTransformer -{ - /// - /// Autoescape for special symbols - /// - /// - /// - public StringBuilder Escape(StringBuilder text) - { - return text.Replace("!", @"\!") - .Replace("*", @"\*") - .Replace("'", @"\'") - .Replace(".", @"\.") - .Replace("+", @"\+") - .Replace("~", @"\~") - .Replace("@", @"\@") - .Replace("_", @"\_") - .Replace("(", @"\(") - .Replace(")", @"\)") - .Replace("-", @"\-") - .Replace("`", @"\`") - .Replace("=", @"\=") - .Replace(">", @"\>") - .Replace("<", @"\<") - .Replace("{", @"\{") - .Replace("}", @"\}") - .Replace("[", @"\[") - .Replace("]", @"\]") - .Replace("|", @"\|") - .Replace("#", @"\#"); - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/VkBot.cs b/Botticelli.Framework.Vk/VkBot.cs deleted file mode 100644 index 380e746d..00000000 --- a/Botticelli.Framework.Vk/VkBot.cs +++ /dev/null @@ -1,334 +0,0 @@ -using Botticelli.Bot.Data.Repositories; -using Botticelli.Client.Analytics; -using Botticelli.Framework.Events; -using Botticelli.Framework.Exceptions; -using Botticelli.Framework.Global; -using Botticelli.Framework.Vk.Messages.API.Requests; -using Botticelli.Framework.Vk.Messages.API.Responses; -using Botticelli.Framework.Vk.Messages.Handlers; -using Botticelli.Interfaces; -using Botticelli.Shared.API; -using Botticelli.Shared.API.Admin.Requests; -using Botticelli.Shared.API.Admin.Responses; -using Botticelli.Shared.API.Client.Requests; -using Botticelli.Shared.API.Client.Responses; -using Botticelli.Shared.Constants; -using Botticelli.Shared.Utils; -using Botticelli.Shared.ValueObjects; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages; - -public class VkBot : BaseBot -{ - private readonly IBotDataAccess _data; - private readonly IBotUpdateHandler _handler; - private readonly MessagePublisher? _messagePublisher; - private readonly LongPollMessagesProvider _messagesProvider; - private readonly VkStorageUploader? _vkUploader; - private bool _eventsAttached; - - public VkBot(LongPollMessagesProvider messagesProvider, - MessagePublisher? messagePublisher, - VkStorageUploader? vkUploader, - IBotDataAccess data, - IBotUpdateHandler handler, - MetricsProcessor metrics, - ILogger logger) : base(logger, metrics) - { - _messagesProvider = messagesProvider; - _messagePublisher = messagePublisher; - _data = data; - _handler = handler; - _vkUploader = vkUploader; - BotUserId = null; // TODO get it from VK - } - - public override BotType Type => BotType.Vk; - - - protected override async Task InnerStopBotAsync(StopBotRequest request, CancellationToken token) - { - try - { - await _messagesProvider.Stop(); - - return StopBotResponse.GetInstance(request.Uid, string.Empty, AdminCommandStatus.Ok); - } - catch (Exception ex) - { - Logger.LogError(ex, ex.Message); - } - - return StopBotResponse.GetInstance(request.Uid, "Error stopping a bot", AdminCommandStatus.Fail); - } - - protected override async Task InnerStartBotAsync(StartBotRequest request, CancellationToken token) - { - try - { - Logger.LogInformation($"{nameof(StartBotAsync)}..."); - var response = StartBotResponse.GetInstance(AdminCommandStatus.Ok, ""); - - if (BotStatusKeeper.IsStarted) - { - Logger.LogInformation($"{nameof(StartBotAsync)}: already started"); - - return response; - } - - if (response.Status != AdminCommandStatus.Ok || BotStatusKeeper.IsStarted) return response; - - if (!_eventsAttached) - { - _messagesProvider.OnUpdates += (args, ct) => - { - var updates = args?.Response?.Updates; - - if (updates == null || !updates.Any()) return; - - _handler.HandleUpdateAsync(updates, ct); - }; - - _eventsAttached = true; - } - - await _messagesProvider.Start(token); - - BotStatusKeeper.IsStarted = true; - Logger.LogInformation($"{nameof(StartBotAsync)}: started"); - - return response; - } - catch (Exception ex) - { - Logger.LogError(ex, ex.Message); - } - - return StartBotResponse.GetInstance(AdminCommandStatus.Fail, "error"); - } - - public override async Task SetBotContext(BotData.Entities.Bot.BotData? context, CancellationToken token) - { - if (context == null) return; - - var currentContext = _data.GetData(); - - if (currentContext?.BotKey != context.BotKey) - { - var stopRequest = StopBotRequest.GetInstance(); - var startRequest = StartBotRequest.GetInstance(); - await StopBotAsync(stopRequest, token); - - _data.SetData(context); - - await _messagesProvider.Stop(); - SetApiKey(context); - - await _messagesProvider.Start(token); - await StartBotAsync(startRequest, token); - } - else - { - SetApiKey(context); - } - } - - private void SetApiKey(BotData.Entities.Bot.BotData? context) - { - _messagesProvider.SetApiKey(context.BotKey); - _messagePublisher.SetApiKey(context.BotKey); - _vkUploader.SetApiKey(context.BotKey); - } - - private string CreateVkAttach(VkSendPhotoResponse fk, string type) - { - return $"{type}" + - $"{fk.Response?.FirstOrDefault()?.OwnerId.ToString()}" + - $"_{fk.Response?.FirstOrDefault()?.Id.ToString()}"; - } - - - private string CreateVkAttach(VkSendVideoResponse fk, string type) - { - return $"{type}" + - $"{fk.Response?.OwnerId.ToString()}" + - $"_{fk.Response?.VideoId.ToString()}"; - } - - - private string CreateVkAttach(VkSendAudioResponse fk, string type) - { - return $"{type}" + - $"{fk.AudioResponseData.AudioMessage.OwnerId}" + - $"_{fk.AudioResponseData.AudioMessage.Id}"; - } - - private string CreateVkAttach(VkSendDocumentResponse fk, string type) - { - return $"{type}" + - $"{fk.DocumentResponseData.Document.OwnerId}" + - $"_{fk.DocumentResponseData.Document.Id}"; - } - - - protected override async Task InnerSendMessageAsync(SendMessageRequest request, - ISendOptionsBuilder? optionsBuilder, - bool isUpdate, - CancellationToken token) - { - foreach (var peerId in request.Message.ChatIds) - try - { - var requests = await CreateRequestsWithAttachments(request, - peerId, - token); - - foreach (var vkRequest in requests) await _messagePublisher.SendAsync(vkRequest, token); - } - catch (Exception? ex) - { - throw new BotException("Can't send a message!", ex); - } - - MessageSent.Invoke(this, - new MessageSentBotEventArgs - { - Message = request.Message - }); - - return new SendMessageResponse(request.Uid, string.Empty); - } - - protected override Task InnerDeleteMessageAsync(RemoveMessageRequest request, - CancellationToken token) - { - throw new NotImplementedException(); - } - - protected override async Task AdditionalProcessing(SendMessageRequest request, - ISendOptionsBuilder? optionsBuilder, - bool isUpdate, - string chatId, - CancellationToken token) - { - Logger.LogError($"{nameof(AdditionalProcessing)} not implemented!"); - } - - private async Task> CreateRequestsWithAttachments(SendMessageRequest request, - string peerId, - CancellationToken token) - { - var currentContext = _data.GetData(); - var result = new List(100); - var first = true; - - currentContext.NotNull(); - currentContext.BotKey.NotNull(); - - if (request.Message.Attachments == null) - { - var vkRequest = new VkSendMessageRequest - { - AccessToken = currentContext.BotKey, - PeerId = peerId, - Body = first ? request.Message.Body : string.Empty, - Lat = request.Message.Location.Latitude, - Long = request.Message.Location.Longitude, - ReplyTo = request.Message.ReplyToMessageUid, - Attachment = null - }; - result.Add(vkRequest); - - return result; - } - - foreach (var attach in request.Message.Attachments) - try - { - var vkRequest = new VkSendMessageRequest - { - AccessToken = currentContext.BotKey, - //UserId = peerId, - Body = first ? request?.Message.Body : string.Empty, - Lat = request?.Message.Location?.Latitude, - Long = request?.Message.Location?.Longitude, - ReplyTo = request?.Message.ReplyToMessageUid, - PeerId = peerId, - Attachment = null - }; - - switch (attach) - { - case BinaryBaseAttachment ba: - { - switch (ba) - { - case {MediaType: MediaType.Image}: - case {MediaType: MediaType.Sticker}: - var sendPhotoResponse = await _vkUploader.SendPhotoAsync(vkRequest, - ba.Name, - ba.Data, - token); - if (sendPhotoResponse != null) vkRequest.Attachment = CreateVkAttach(sendPhotoResponse, "photo"); - - break; - case {MediaType: MediaType.Video}: - //var sendVideoResponse = await _vkUploader.SendVideoAsync(vkRequest, - // ba.Name, - // ba.Data, - // token); - - //if (sendVideoResponse != default) vkRequest.Attachment = CreateVkAttach(sendVideoResponse, currentContext, "video"); - - break; - case {MediaType: MediaType.Voice}: - case {MediaType: MediaType.Audio}: - var sendAudioMessageResponse = await _vkUploader.SendAudioMessageAsync(vkRequest, - ba.Name, - ba.Data, - token); - if (sendAudioMessageResponse != null) vkRequest.Attachment = CreateVkAttach(sendAudioMessageResponse, "doc"); - - - break; - case {MediaType: MediaType.Document}: - var sendDocMessageResponse = await _vkUploader.SendDocsMessageAsync(vkRequest, - ba.Name, - ba.Data, - token); - if (sendDocMessageResponse != null) vkRequest.Attachment = CreateVkAttach(sendDocMessageResponse, "doc"); - - - break; - } - } - - break; - case InvoiceBaseAttachment: - // Not implemented - break; - } - - result.Add(vkRequest); - first = false; - } - catch (Exception ex) - { - Logger.LogInformation($"Error sending a message with attach: {attach.Uid}", ex); - } - - return result; - } - - public override Task DeleteMessageAsync(RemoveMessageRequest request, - CancellationToken token) - { - throw new NotImplementedException(); - } - - public override event MsgSentEventHandler MessageSent; - public override event MsgReceivedEventHandler MessageReceived; - public override event MsgRemovedEventHandler MessageRemoved; - public virtual event MessengerSpecificEventHandler MessengerSpecificEvent; -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/VkStorageUploader.cs b/Botticelli.Framework.Vk/VkStorageUploader.cs deleted file mode 100644 index deafc66d..00000000 --- a/Botticelli.Framework.Vk/VkStorageUploader.cs +++ /dev/null @@ -1,386 +0,0 @@ -using System.Net.Http.Json; -using Botticelli.Audio; -using Botticelli.Framework.Exceptions; -using Botticelli.Framework.Vk.Messages.API.Requests; -using Botticelli.Framework.Vk.Messages.API.Responses; -using Botticelli.Framework.Vk.Messages.API.Utils; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Vk.Messages; - -/// -/// VK storage upload component -/// -public class VkStorageUploader -{ - private readonly IConvertor _audioConvertor; - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; - private string _apiKey; - - public VkStorageUploader(IHttpClientFactory httpClientFactory, - IConvertor audioConvertor, - ILogger logger) - { - _httpClientFactory = httpClientFactory; - _audioConvertor = audioConvertor; - _logger = logger; - } - - private string ApiVersion => "5.199"; - - public void SetApiKey(string key) - { - _apiKey = key; - } - - /// - /// Get an upload address for a photo - /// - /// - /// - /// - private async Task GetPhotoUploadAddress(VkSendMessageRequest vkMessageRequest, - CancellationToken token) - { - try - { - using var httpClient = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Get, - ApiUtils.GetMethodUri("https://api.vk.com", - "photos.getMessagesUploadServer", - new - { - access_token = _apiKey, - v = ApiVersion, - peer_id = vkMessageRequest.PeerId - })); - - var response = await httpClient.SendAsync(request, token); - - return await response.Content.ReadFromJsonAsync(token); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting an upload address!"); - } - - return null; - } - - - /// - /// Get an upload address for an audio - /// - /// - /// - /// - private async Task GetAudioUploadAddress(VkSendMessageRequest vkMessageRequest, - CancellationToken token) - { - return await GetDocsUploadAddress(vkMessageRequest, "audio_message", token); - } - - private async Task GetDocsUploadAddress(VkSendMessageRequest vkMessageRequest, - string type, - CancellationToken token) - { - try - { - using var httpClient = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Get, - ApiUtils.GetMethodUri("https://api.vk.com", - "docs.getMessagesUploadServer", - new - { - access_token = _apiKey, - v = ApiVersion, - peer_id = vkMessageRequest.PeerId, - type - })); - - var response = await httpClient.SendAsync(request, token); - - return await response.Content.ReadFromJsonAsync(token); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting an upload address!"); - } - - return null; - } - - /// - /// Uploads a photo (binaries) - /// - /// - /// - /// - /// - /// - private async Task UploadPhoto(string uploadUrl, - string name, - byte[] binaryContent, - CancellationToken token) - { - using var httpClient = _httpClientFactory.CreateClient(); - using var memoryContentStream = new MemoryStream(binaryContent); - memoryContentStream.Seek(0, SeekOrigin.Begin); - - var content = new MultipartFormDataContent {{new StreamContent(memoryContentStream), "photo", name}}; - - var response = await httpClient.PostAsync(uploadUrl, content, token); - - return await response.Content.ReadFromJsonAsync(token); - } - - - /// - /// Uploads an audio message (binaries) - /// - /// - /// - /// - /// - /// - private async Task UploadAudioMessage(string uploadUrl, - string name, - byte[] binaryContent, - CancellationToken token) - { - // convert to ogg in order to meet VK requirements - var oggContent = _audioConvertor.Convert(binaryContent, - new AudioInfo - { - AudioFormat = AudioFormat.Ogg - }); - - return await PushDocument(uploadUrl, - name, - oggContent, - token); - } - - private async Task PushDocument(string uploadUrl, - string name, - byte[] binContent, - CancellationToken token) - { - using var httpClient = _httpClientFactory.CreateClient(); - var content = new MultipartFormDataContent - { - { - new ByteArrayContent(binContent, 0, binContent.Length), - "file", - $"{Path.GetFileNameWithoutExtension(name)}{Guid.NewGuid()}.{Path.GetExtension(name)}" - } - }; - - var response = await httpClient.PostAsync(uploadUrl, content, token); - - return await response.Content.ReadFromJsonAsync(token); - } - - /// - /// Uploads a document message (binaries) - /// - /// - /// - /// - /// - /// - private async Task UploadDocMessage(string uploadUrl, - string name, - byte[] binaryContent, - CancellationToken token) - { - return await PushDocument(uploadUrl, - name, - binaryContent, - token); - } - - /// - /// Uploads a video (binaries) - /// - /// - /// - /// - /// - /// - private async Task UploadVideo(string uploadUrl, - string name, - byte[] binaryContent, - CancellationToken token) - { - using var httpClient = _httpClientFactory.CreateClient(); - using var memoryContentStream = new MemoryStream(binaryContent); - memoryContentStream.Seek(0, SeekOrigin.Begin); - - var content = new MultipartFormDataContent {{new StreamContent(memoryContentStream), "video", name}}; - - var response = await httpClient.PostAsync(uploadUrl, content, token); - - return await response.Content.ReadFromJsonAsync(token); - } - - /// - /// The main public method for sending a photo - /// - /// - /// - /// - /// - /// - /// - public async Task SendPhotoAsync(VkSendMessageRequest vkMessageRequest, - string name, - byte[] binaryContent, - CancellationToken token) - { - try - { - var address = await GetPhotoUploadAddress(vkMessageRequest, token); - - if (address?.Response == null) throw new BotException("Sending photo error: no upload server address!"); - - var uploadedPhoto = await UploadPhoto(address.Response.UploadUrl, - name, - binaryContent, - token); - - if (uploadedPhoto?.Photo == null) throw new BotException("Sending photo error: no media uploaded!"); - - using var httpClient = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Post, - ApiUtils.GetMethodUri("https://api.vk.com", - "photos.saveMessagesPhoto", - new - { - server = uploadedPhoto.Server, - hash = uploadedPhoto.Hash, - access_token = _apiKey, - v = ApiVersion - })); - request.Content = ApiUtils.GetMethodMultipartFormContent(new - { - photo = uploadedPhoto.Photo - }); - - var response = await httpClient.SendAsync(request, token); - - return await response.Content.ReadFromJsonAsync(token); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error uploading media"); - } - - return null; - } - - - /// - /// The main public method for sending an audio message - /// - /// - /// - /// - /// - /// - /// - public async Task SendAudioMessageAsync(VkSendMessageRequest vkMessageRequest, - string name, - byte[] binaryContent, - CancellationToken token) - { - try - { - var address = await GetAudioUploadAddress(vkMessageRequest, token); - - if (address?.Response == null) throw new BotException("Sending audio error: no upload server address!"); - - var uploadedAudio = await UploadAudioMessage(address.Response.UploadUrl, - name, - binaryContent, - token); - - if (uploadedAudio?.File == null) throw new BotException("Sending audio error: no media uploaded!"); - - using var httpClient = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Post, - ApiUtils.GetMethodUri("https://api.vk.com", - "docs.save", - new - { - title = "voice", - tags = "string.Empty", - file = uploadedAudio.File, - // audio = uploadedAudio.File, - access_token = _apiKey, - v = ApiVersion - })); - request.Content = ApiUtils.GetMethodMultipartFormContent(new - { - audio = uploadedAudio.File - }); - - var response = await httpClient.SendAsync(request, token); - - return await response.Content.ReadFromJsonAsync(token); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error uploading media"); - } - - return null; - } - - - public async Task SendDocsMessageAsync(VkSendMessageRequest vkMessageRequest, - string name, - byte[] binaryContent, - CancellationToken token) - { - try - { - var address = await GetDocsUploadAddress(vkMessageRequest, "doc", token); - - if (address?.Response == null) throw new BotException("Sending doc error: no upload server address!"); - - var uploadedDoc = await UploadDocMessage(address.Response.UploadUrl, - name, - binaryContent, - token); - - if (uploadedDoc?.File == null) throw new BotException("Sending doc error: no file uploaded!"); - - using var httpClient = _httpClientFactory.CreateClient(); - var request = new HttpRequestMessage(HttpMethod.Post, - ApiUtils.GetMethodUri("https://api.vk.com", - "docs.save", - new - { - file = uploadedDoc.File, - access_token = _apiKey, - v = ApiVersion - })); - request.Content = ApiUtils.GetMethodMultipartFormContent(new - { - doc = uploadedDoc.File - }); - - var response = await httpClient.SendAsync(request, token); - - return await response.Content.ReadFromJsonAsync(token); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error uploading media"); - } - - return null; - } -} \ No newline at end of file diff --git a/Botticelli.Framework.Vk/vk_test.txt b/Botticelli.Framework.Vk/vk_test.txt deleted file mode 100644 index f589148a..00000000 --- a/Botticelli.Framework.Vk/vk_test.txt +++ /dev/null @@ -1 +0,0 @@ -vk1.a.Hd7QxN4U9RKGNHK2AwtpbQ-LJq0Hvh18Z5hw2mi1rbrxmHYHawp4yjqltdbS5IrVJVmqojGDKNPuvOWTVOp72CVf2fEFKEqU1h374jf7dclSTVv_aQEUGXKNRWVKcg9Crtg7e2hwRN-YZORDiSxldYTlnuvTzI7R6wnTG1xusVRVM7xG2dt-NN4p8ShCMdqWfJ4VkQ2WtfjfvC6t8xRRqA \ No newline at end of file diff --git a/Botticelli.Framework/BaseBot.cs b/Botticelli.Framework/BaseBot.cs index 439e3280..a4179f88 100644 --- a/Botticelli.Framework/BaseBot.cs +++ b/Botticelli.Framework/BaseBot.cs @@ -28,10 +28,14 @@ public abstract class BaseBot public delegate void StartedEventHandler(object sender, StartedBotEventArgs e); public delegate void StoppedEventHandler(object sender, StoppedBotEventArgs e); - + public delegate void ContactSharedEventHandler(object sender, SharedContactBotEventArgs e); + public delegate void NewChatMembersEventHandler(object sender, NewChatMembersBotEventArgs e); + public virtual event MsgSentEventHandler? MessageSent; public virtual event MsgReceivedEventHandler? MessageReceived; public virtual event MsgRemovedEventHandler? MessageRemoved; + public virtual event ContactSharedEventHandler? ContactShared; + public virtual event NewChatMembersEventHandler? NewChatMembers; } /// @@ -43,10 +47,10 @@ public abstract class BaseBot : BaseBot, IBot { public delegate void MessengerSpecificEventHandler(object sender, MessengerSpecificBotEventArgs e); - private readonly MetricsProcessor _metrics; + private readonly MetricsProcessor? _metrics; protected readonly ILogger Logger; - protected BaseBot(ILogger logger, MetricsProcessor metrics) + protected BaseBot(ILogger logger, MetricsProcessor? metrics) { Logger = logger; _metrics = metrics; @@ -56,7 +60,7 @@ public virtual async Task StartBotAsync(StartBotRequest reques { if (BotStatusKeeper.IsStarted) return StartBotResponse.GetInstance(request.Uid, string.Empty, AdminCommandStatus.Ok); - _metrics.Process(MetricNames.BotStarted, BotDataUtils.GetBotId()); + _metrics?.Process(MetricNames.BotStarted, BotDataUtils.GetBotId()); var result = await InnerStartBotAsync(request, token); @@ -67,7 +71,7 @@ public virtual async Task StartBotAsync(StartBotRequest reques public virtual async Task StopBotAsync(StopBotRequest request, CancellationToken token) { - _metrics.Process(MetricNames.BotStopped, BotDataUtils.GetBotId()); + _metrics?.Process(MetricNames.BotStopped, BotDataUtils.GetBotId()); if (!BotStatusKeeper.IsStarted) return StopBotResponse.GetInstance(request.Uid, string.Empty, AdminCommandStatus.Ok); @@ -78,7 +82,7 @@ public virtual async Task StopBotAsync(StopBotRequest request, return result; } - public abstract Task SetBotContext(BotData.Entities.Bot.BotData? botData, CancellationToken token); + public abstract Task SetBotContext(BotData.Entities.Bot.BotData? context, CancellationToken token); /// /// Sends a message @@ -104,7 +108,7 @@ public virtual async Task SendMessageAsync(Se CancellationToken token) where TSendOptions : class { - _metrics.Process(MetricNames.MessageSent, BotDataUtils.GetBotId()); + _metrics?.Process(MetricNames.MessageSent, BotDataUtils.GetBotId()); return await InnerSendMessageAsync(request, optionsBuilder, @@ -122,7 +126,7 @@ public async Task UpdateMessageAsync(SendMess CancellationToken token) where TSendOptions : class { - _metrics.Process(MetricNames.MessageSent, BotDataUtils.GetBotId()); + _metrics?.Process(MetricNames.MessageSent, BotDataUtils.GetBotId()); return await InnerSendMessageAsync(request, optionsBuilder, @@ -130,21 +134,16 @@ public async Task UpdateMessageAsync(SendMess token); } - public virtual async Task DeleteMessageAsync(RemoveMessageRequest request, + public virtual async Task DeleteMessageAsync(DeleteMessageRequest request, CancellationToken token) { - _metrics.Process(MetricNames.MessageRemoved, BotDataUtils.GetBotId()); + _metrics?.Process(MetricNames.MessageRemoved, BotDataUtils.GetBotId()); return await InnerDeleteMessageAsync(request, token); } public abstract BotType Type { get; } - public string BotUserId { get; set; } - - public Task PingAsync(PingRequest request) - { - return Task.FromResult(PingResponse.GetInstance(request.Uid)); - } + public string? BotUserId { get; set; } protected abstract Task InnerStartBotAsync(StartBotRequest request, CancellationToken token); @@ -156,24 +155,9 @@ protected abstract Task InnerSendMessageAsync CancellationToken token) where TSendOptions : class; - protected abstract Task InnerDeleteMessageAsync(RemoveMessageRequest request, + protected abstract Task InnerDeleteMessageAsync(DeleteMessageRequest request, CancellationToken token); - /// - /// Additional message processing while sending a message - /// - /// - /// - /// - /// - /// - /// - protected abstract Task AdditionalProcessing(SendMessageRequest request, - ISendOptionsBuilder? optionsBuilder, - bool isUpdate, - string chatId, - CancellationToken token); - - public event StartedEventHandler Started; - public event StoppedEventHandler Stopped; + public event StartedEventHandler? Started; + public event StoppedEventHandler? Stopped; } \ No newline at end of file diff --git a/Botticelli.Framework/Botticelli.Framework.csproj b/Botticelli.Framework/Botticelli.Framework.csproj index 9477f10a..48e2c495 100644 --- a/Botticelli.Framework/Botticelli.Framework.csproj +++ b/Botticelli.Framework/Botticelli.Framework.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli.Framework BotticelliBots new_logo_compact.png diff --git a/Botticelli.Framework/Builders/BotBuilder.cs b/Botticelli.Framework/Builders/BotBuilder.cs index 4dbed717..1eef12eb 100644 --- a/Botticelli.Framework/Builders/BotBuilder.cs +++ b/Botticelli.Framework/Builders/BotBuilder.cs @@ -9,57 +9,94 @@ public abstract class BotBuilder { protected abstract void Assert(); - public TBot? Build() + public virtual TBot? Build(IServiceProvider serviceProvider) { Assert(); - return InnerBuild(); + return InnerBuild(serviceProvider); } - protected abstract TBot? InnerBuild(); + protected abstract TBot? InnerBuild(IServiceProvider serviceProvider); } -public abstract class BotBuilder : BotBuilder - where TBotBuilder : BotBuilder +public abstract class BotBuilder : BotBuilder, IBotBuilder + where TBotBuilder : BotBuilder { - private readonly ServerSettings _serverSettings; - protected AnalyticsClientSettingsBuilder AnalyticsClientSettingsBuilder; - protected DataAccessSettingsBuilder BotDataAccessSettingsBuilder; - protected ServerSettingsBuilder ServerSettingsBuilder; - protected IServiceCollection? Services; + protected AnalyticsClientSettingsBuilder? AnalyticsClientSettingsBuilder; + protected DataAccessSettingsBuilder? BotDataAccessSettingsBuilder; + protected ServerSettingsBuilder? ServerSettingsBuilder; + public IServiceCollection Services = null!; + + protected BaseBot.MsgSentEventHandler? MessageSent; + protected BaseBot.MsgReceivedEventHandler? MessageReceived; + protected BaseBot.MsgRemovedEventHandler? MessageRemoved; + protected BaseBot.ContactSharedEventHandler? SharedContact; + protected BaseBot.NewChatMembersEventHandler? NewChatMembers; protected override void Assert() { } - protected TBotBuilder AddServices(IServiceCollection services) + public virtual BotBuilder AddServices(IServiceCollection services) { Services = services; - return (this as TBotBuilder)!; + return this; } - public abstract TBotBuilder AddBotSettings(BotSettingsBuilder settingsBuilder) - where TBotSettings : BotSettings, new(); - - public TBotBuilder AddAnalyticsSettings(AnalyticsClientSettingsBuilder clientSettingsBuilder) + public virtual BotBuilder AddAnalyticsSettings(AnalyticsClientSettingsBuilder clientSettingsBuilder) { AnalyticsClientSettingsBuilder = clientSettingsBuilder; - return (this as TBotBuilder)!; + return this; } - public TBotBuilder AddServerSettings(ServerSettingsBuilder settingsBuilder) + protected virtual BotBuilder AddServerSettings(ServerSettingsBuilder settingsBuilder) { ServerSettingsBuilder = settingsBuilder; - return (this as TBotBuilder)!; + return this; } - public TBotBuilder AddBotDataAccessSettings(DataAccessSettingsBuilder botDataAccessBuilder) + public virtual BotBuilder AddBotDataAccessSettings(DataAccessSettingsBuilder botDataAccessBuilder) { BotDataAccessSettingsBuilder = botDataAccessBuilder; - return (this as TBotBuilder)!; + return this; + } + + public virtual BotBuilder AddOnMessageSent(BaseBot.MsgSentEventHandler handler) + { + MessageSent += handler; + + return this; + } + + public virtual BotBuilder AddOnMessageReceived(BaseBot.MsgReceivedEventHandler handler) + { + MessageReceived += handler; + + return this; + } + + public virtual BotBuilder AddOnMessageRemoved(BaseBot.MsgRemovedEventHandler handler) + { + MessageRemoved += handler; + + return this; + } + + public virtual BotBuilder AddNewChatMembers(BaseBot.NewChatMembersEventHandler handler) + { + NewChatMembers += handler; + + return this; + } + + public virtual BotBuilder AddSharedContact(BaseBot.ContactSharedEventHandler handler) + { + SharedContact += handler; + + return this; } } \ No newline at end of file diff --git a/Botticelli.Framework/Builders/IBotBuilder.cs b/Botticelli.Framework/Builders/IBotBuilder.cs new file mode 100644 index 00000000..fa750f11 --- /dev/null +++ b/Botticelli.Framework/Builders/IBotBuilder.cs @@ -0,0 +1,18 @@ +using Botticelli.Bot.Data.Settings; +using Botticelli.Client.Analytics.Settings; +using Microsoft.Extensions.DependencyInjection; + +namespace Botticelli.Framework.Builders; + +public interface IBotBuilder where TBotBuilder : BotBuilder +{ + BotBuilder AddServices(IServiceCollection services); + BotBuilder AddAnalyticsSettings(AnalyticsClientSettingsBuilder clientSettingsBuilder); + BotBuilder AddBotDataAccessSettings(DataAccessSettingsBuilder botDataAccessBuilder); + BotBuilder AddOnMessageSent(BaseBot.MsgSentEventHandler handler); + BotBuilder AddOnMessageReceived(BaseBot.MsgReceivedEventHandler handler); + BotBuilder AddOnMessageRemoved(BaseBot.MsgRemovedEventHandler handler); + BotBuilder AddNewChatMembers(BaseBot.NewChatMembersEventHandler handler); + BotBuilder AddSharedContact(BaseBot.ContactSharedEventHandler handler); + TBot? Build(IServiceProvider serviceProvider); +} \ No newline at end of file diff --git a/Botticelli.Framework/Commands/Processors/CommandChainFirstElementProcessor.cs b/Botticelli.Framework/Commands/Processors/CommandChainFirstElementProcessor.cs index 624c5c10..f0da2b77 100644 --- a/Botticelli.Framework/Commands/Processors/CommandChainFirstElementProcessor.cs +++ b/Botticelli.Framework/Commands/Processors/CommandChainFirstElementProcessor.cs @@ -21,8 +21,6 @@ public CommandChainFirstElementProcessor(ILogger> logge IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { } diff --git a/Botticelli.Framework/Commands/Processors/CommandChainProcessorBuilder.cs b/Botticelli.Framework/Commands/Processors/CommandChainProcessorBuilder.cs index a2e5abd8..e523517e 100644 --- a/Botticelli.Framework/Commands/Processors/CommandChainProcessorBuilder.cs +++ b/Botticelli.Framework/Commands/Processors/CommandChainProcessorBuilder.cs @@ -1,3 +1,4 @@ +using Botticelli.Framework.Extensions.Processors; using Microsoft.Extensions.DependencyInjection; namespace Botticelli.Framework.Commands.Processors; @@ -13,26 +14,26 @@ public CommandChainProcessorBuilder(IServiceCollection services) _services = services; _typesChain.Add(typeof(CommandChainFirstElementProcessor)); - _services.AddScoped>(); + _services.AddSingleton>(); + ProcessorFactoryBuilder.AddProcessor>(_services); } public CommandChainProcessorBuilder AddNext() where TNextProcessor : class, ICommandChainProcessor { _typesChain.Add(typeof(TNextProcessor)); - _services.AddScoped(); + _services.AddSingleton(); + ProcessorFactoryBuilder.AddProcessor(_services); return this; } - public ICommandChainProcessor? Build() + public ICommandChainProcessor? Build(IServiceProvider sp) { if (_typesChain.Count == 0) return null; // initializing chain processors... - var sp = _services.BuildServiceProvider(); - _chainProcessor ??= sp.GetRequiredService(_typesChain.First()) as ICommandChainProcessor; // making a chain... diff --git a/Botticelli.Framework/Commands/Processors/CommandProcessor.cs b/Botticelli.Framework/Commands/Processors/CommandProcessor.cs index 51071553..e3e44e2f 100644 --- a/Botticelli.Framework/Commands/Processors/CommandProcessor.cs +++ b/Botticelli.Framework/Commands/Processors/CommandProcessor.cs @@ -4,6 +4,7 @@ using Botticelli.Client.Analytics; using Botticelli.Framework.Commands.Utils; using Botticelli.Framework.Commands.Validators; +using Botticelli.Framework.SendOptions; using Botticelli.Interfaces; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; @@ -18,14 +19,24 @@ public abstract class CommandProcessor : ICommandProcessor private readonly string _command; private readonly ICommandValidator _commandValidator; private readonly IValidator _messageValidator; - private readonly MetricsProcessor _metricsProcessor; + private readonly MetricsProcessor? _metricsProcessor; protected readonly ILogger Logger; - protected IBot Bot; + private IBot? _bot; protected CommandProcessor(ILogger logger, ICommandValidator commandValidator, - MetricsProcessor metricsProcessor, IValidator messageValidator) + { + Logger = logger; + _commandValidator = commandValidator; + _messageValidator = messageValidator; + _command = GetOldFashionedCommandName(typeof(TCommand).Name); + } + + protected CommandProcessor(ILogger logger, + ICommandValidator commandValidator, + IValidator messageValidator, + MetricsProcessor? metricsProcessor) { Logger = logger; _commandValidator = commandValidator; @@ -42,8 +53,9 @@ public virtual async Task ProcessAsync(Message message, CancellationToken token) if (!messageValidationResult.IsValid) { - _metricsProcessor.Process(MetricNames.BotError, BotDataUtils.GetBotId()); - Logger.LogError($"Error in {GetType().Name} invalid input message: {messageValidationResult.Errors.Select(e => $"({e.PropertyName} : {e.ErrorCode} : {e.ErrorMessage})")}"); + _metricsProcessor?.Process(MetricNames.BotError, BotDataUtils.GetBotId()); + Logger.LogError($"Error in {GetType().Name} invalid input message:" + + $" {messageValidationResult.Errors.Select(e => $"({e.PropertyName} : {e.ErrorCode} : {e.ErrorMessage})")}"); return; } @@ -55,7 +67,7 @@ public virtual async Task ProcessAsync(Message message, CancellationToken token) return; } - if (message.From!.Id!.Equals(Bot.BotUserId, StringComparison.InvariantCulture)) return; + if (message.From?.Id != null && message.From!.Id!.Equals(_bot?.BotUserId, StringComparison.InvariantCulture)) return; Classify(ref message); @@ -66,7 +78,7 @@ public virtual async Task ProcessAsync(Message message, CancellationToken token) message.Poll == null && message.CallbackData == null) { - Logger.LogWarning("Message {msgId} is empty! Skipping...", message.Uid); + Logger.LogWarning("Message {MsgId} is empty! Skipping...", message.Uid); return; } @@ -117,8 +129,8 @@ await ValidateAndProcess(message, } catch (Exception ex) { - _metricsProcessor.Process(MetricNames.BotError, BotDataUtils.GetBotId()); - Logger.LogError(ex, $"Error in {GetType().Name}: {ex.Message}"); + _metricsProcessor?.Process(MetricNames.BotError, BotDataUtils.GetBotId()); + Logger.LogError(ex, "Error in {Name}: {ExMessage}", GetType().Name, ex.Message); await InnerProcessError(message, ex, token); } @@ -127,20 +139,18 @@ await ValidateAndProcess(message, public virtual void SetBot(IBot bot) { - Bot = bot; + _bot = bot; } public void SetServiceProvider(IServiceProvider sp) { } - protected void Classify(ref Message message) + protected static void Classify(ref Message message) { var body = GetBody(message); - if (CommandUtils.SimpleCommandRegex.IsMatch(body)) - message.Type = Message.MessageType.Command; - else if (CommandUtils.ArgsCommandRegex.IsMatch(body)) + if (CommandUtils.SimpleCommandRegex.IsMatch(body) || CommandUtils.ArgsCommandRegex.IsMatch(body)) message.Type = Message.MessageType.Command; else message.Type = Message.MessageType.Messaging; @@ -155,12 +165,13 @@ private static string GetBody(Message message) private void SendMetric(string metricName) { - _metricsProcessor.Process(metricName, BotDataUtils.GetBotId()!); + _metricsProcessor?.Process(metricName, BotDataUtils.GetBotId()!); } private void SendMetric() { - _metricsProcessor.Process(GetOldFashionedCommandName($"{GetType().Name.Replace("Processor", string.Empty)}Command"), BotDataUtils.GetBotId()!); + _metricsProcessor?.Process(GetOldFashionedCommandName($"{GetType().Name.Replace("Processor", string.Empty)}Command"), + BotDataUtils.GetBotId()!); } private string GetOldFashionedCommandName(string fullCommand) @@ -171,6 +182,8 @@ private string GetOldFashionedCommandName(string fullCommand) private async Task ValidateAndProcess(Message message, CancellationToken token) { + if (_bot == null) return; + if (message.Type == Message.MessageType.Messaging) { SendMetric(); @@ -195,10 +208,76 @@ private async Task ValidateAndProcess(Message message, } }; - await Bot.SendMessageAsync(errMessageRequest, token); + await SendMessage(errMessageRequest, token); } } + protected async Task DeleteMessage(DeleteMessageRequest request, CancellationToken token) + { + if (_bot == null) return; + + await _bot.DeleteMessageAsync(request, token); + } + + protected async Task DeleteMessage(Message message, CancellationToken token) + { + if (_bot == null) return; + + foreach (var request in message.ChatIds.Select(chatId => new DeleteMessageRequest(message.Uid, chatId))) await _bot.DeleteMessageAsync(request, token); + } + + protected async Task SendMessage(Message message, CancellationToken token) + { + if (_bot == null) return; + + var request = new SendMessageRequest + { + Message = message + }; + + await SendMessage(request, token); + } + + protected async Task SendMessage(SendMessageRequest request, CancellationToken token) + { + if (_bot == null) return; + + await _bot.SendMessageAsync(request, token); + } + + protected async Task SendMessage(SendMessageRequest request, + SendOptionsBuilder? options, + CancellationToken token) + where TReplyMarkup : class + { + if (_bot == null) return; + + await _bot.SendMessageAsync(request, options, token); + } + + protected async Task UpdateMessage(Message message, + ISendOptionsBuilder? options, + CancellationToken token) + where TSendOptions : class + { + if (_bot == null) return; + + await _bot.UpdateMessageAsync(new SendMessageRequest + { + ExpectPartialResponse = false, + Message = new Message + { + Body = message.CallbackData, + Uid = message.Uid, + ChatIds = message.ChatIds, + ChatIdInnerIdLinks = message.ChatIdInnerIdLinks + } + }, + options, + token); + } + + protected virtual Task InnerProcessContact(Message message, CancellationToken token) { return Task.CompletedTask; diff --git a/Botticelli.Framework/Commands/Processors/WaitForClientResponseCommandChainProcessor.cs b/Botticelli.Framework/Commands/Processors/WaitForClientResponseCommandChainProcessor.cs index 58cdef7d..f5ada24c 100644 --- a/Botticelli.Framework/Commands/Processors/WaitForClientResponseCommandChainProcessor.cs +++ b/Botticelli.Framework/Commands/Processors/WaitForClientResponseCommandChainProcessor.cs @@ -1,6 +1,5 @@ using Botticelli.Client.Analytics; using Botticelli.Framework.Commands.Validators; -using Botticelli.Interfaces; using Botticelli.Shared.ValueObjects; using FluentValidation; using Microsoft.Extensions.Logging; @@ -21,18 +20,13 @@ protected WaitForClientResponseCommandChainProcessor(ILogger messageValidator) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { } private TimeSpan Timeout { get; } = TimeSpan.FromMinutes(10); - public virtual void SetBot(IBot bot) - { - Bot = bot; - } - public override async Task ProcessAsync(Message message, CancellationToken token) { // filters 'not our' chains diff --git a/Botticelli.Framework/Extensions/Processors/ProcessorFactoryBuilder.cs b/Botticelli.Framework/Extensions/Processors/ProcessorFactoryBuilder.cs index 5e391a10..884e2838 100644 --- a/Botticelli.Framework/Extensions/Processors/ProcessorFactoryBuilder.cs +++ b/Botticelli.Framework/Extensions/Processors/ProcessorFactoryBuilder.cs @@ -7,7 +7,7 @@ namespace Botticelli.Framework.Extensions.Processors; public static class ProcessorFactoryBuilder { private static IServiceCollection? _serviceCollection; - private static readonly List ProcessorTypes = new(); + private static readonly List ProcessorTypes = []; public static void AddProcessor(IServiceCollection serviceCollection) where TProcessor : class, ICommandProcessor @@ -16,11 +16,10 @@ public static void AddProcessor(IServiceCollection serviceCollection ProcessorTypes.Add(typeof(TProcessor)); } - public static ProcessorFactory Build() + public static ProcessorFactory Build(IServiceProvider sp) { - if (_serviceCollection == null) throw new NullReferenceException("Service collection is null! PLease, call AddProcessor() first!"); - - var sp = _serviceCollection.BuildServiceProvider(); + if (_serviceCollection == null) + return new ProcessorFactory([]); var processors = ProcessorTypes .Select(pt => diff --git a/Botticelli.Framework/Extensions/StartupExtensions.cs b/Botticelli.Framework/Extensions/StartupExtensions.cs index 34848c51..b5b3893e 100644 --- a/Botticelli.Framework/Extensions/StartupExtensions.cs +++ b/Botticelli.Framework/Extensions/StartupExtensions.cs @@ -42,10 +42,11 @@ public static IServiceCollection AddBotticelliFramework(this IServiceCollection public static CommandAddServices AddBotCommand(this IServiceCollection services) where TCommand : class, ICommand { + var cmd = new CommandAddServices(services); services.AddScoped() - .AddSingleton>(_ => new CommandAddServices(services)); + .AddSingleton>(_ => cmd); - return services.BuildServiceProvider().GetRequiredService>(); + return cmd; } public static IServiceCollection AddBotCommand(this IS where TBot : IBot { var commandChainProcessorBuilder = sp.GetRequiredService>(); - commandChainProcessorBuilder.Build(); + commandChainProcessorBuilder.Build(sp); return sp; } diff --git a/Botticelli.Framework/Global/BotStatusKeeper.cs b/Botticelli.Framework/Global/BotStatusKeeper.cs index 085d239a..ec5bb150 100644 --- a/Botticelli.Framework/Global/BotStatusKeeper.cs +++ b/Botticelli.Framework/Global/BotStatusKeeper.cs @@ -2,5 +2,5 @@ public static class BotStatusKeeper { - public static bool IsStarted = false; + public static bool IsStarted { get; set; } } \ No newline at end of file diff --git a/Botticelli.Framework/Options/BotDataSettings.cs b/Botticelli.Framework/Options/BotDataSettings.cs new file mode 100644 index 00000000..31b728b8 --- /dev/null +++ b/Botticelli.Framework/Options/BotDataSettings.cs @@ -0,0 +1,12 @@ +namespace Botticelli.Framework.Options; + +/// +/// Bot data/context settings +/// +public class BotDataSettings +{ + public const string Section = "BotData"; + + public string? BotId { get; set; } + public string? BotKey { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Framework/Options/BotDataSettingsBuilder.cs b/Botticelli.Framework/Options/BotDataSettingsBuilder.cs new file mode 100644 index 00000000..d474fad0 --- /dev/null +++ b/Botticelli.Framework/Options/BotDataSettingsBuilder.cs @@ -0,0 +1,30 @@ +namespace Botticelli.Framework.Options; + +public class BotDataSettingsBuilder + where T : BotDataSettings, new() +{ + private T? _settings; + + public static BotDataSettingsBuilder Instance() + { + return new BotDataSettingsBuilder(); + } + + + public void Set(T? settings) + { + _settings = settings; + } + + public BotDataSettingsBuilder Set(Action func) + { + func(_settings); + + return this; + } + + public T? Build() + { + return _settings; + } +} \ No newline at end of file diff --git a/Botticelli.Framework/SendOptions/SendOptionsBuilder.cs b/Botticelli.Framework/SendOptions/SendOptionsBuilder.cs index 2429c67a..25da06cc 100644 --- a/Botticelli.Framework/SendOptions/SendOptionsBuilder.cs +++ b/Botticelli.Framework/SendOptions/SendOptionsBuilder.cs @@ -55,7 +55,7 @@ public static SendOptionsBuilder CreateBuilder() return new SendOptionsBuilder(); } - public static SendOptionsBuilder CreateBuilder(T input) + public static SendOptionsBuilder? CreateBuilder(T input) { return new SendOptionsBuilder(input); } diff --git a/Botticelli.Framework/Services/BotActualizationService.cs b/Botticelli.Framework/Services/BotActualizationService.cs index be8cd472..c2edaf1e 100644 --- a/Botticelli.Framework/Services/BotActualizationService.cs +++ b/Botticelli.Framework/Services/BotActualizationService.cs @@ -19,19 +19,35 @@ public abstract class BotActualizationService : IHostedService protected readonly string? BotId = BotDataUtils.GetBotId(); protected readonly IHttpClientFactory HttpClientFactory; protected readonly ILogger Logger; - protected readonly ServerSettings ServerSettings; + protected readonly ServerSettings? ServerSettings; /// /// This service is intended for sending keepalive/hello messages /// to Botticelli Admin server and receiving status messages from it /// protected BotActualizationService(IHttpClientFactory httpClientFactory, - ServerSettings serverSettings, IBot bot, ILogger logger) { HttpClientFactory = httpClientFactory; + Bot = bot; + Logger = logger; + + ActualizationEvent.Reset(); + } + + + /// + /// This service is intended for sending keepalive/hello messages + /// to Botticelli Admin server and receiving status messages from it + /// + protected BotActualizationService(IHttpClientFactory httpClientFactory, + IBot bot, + ILogger logger, + ServerSettings? serverSettings) + { ServerSettings = serverSettings; + HttpClientFactory = httpClientFactory; Bot = bot; Logger = logger; @@ -66,9 +82,9 @@ public virtual Task StopAsync(CancellationToken cancellationToken) var content = JsonContent.Create(request); - Logger.LogDebug("InnerSend request: {request}", request); + Logger.LogDebug("InnerSend request: {Request}", request); - var response = await httpClient.PostAsync(Url.Combine(ServerSettings.ServerUri, funcName), + var response = await httpClient.PostAsync(Url.Combine(ServerSettings?.ServerUri, funcName), content, cancellationToken); diff --git a/Botticelli.Framework/Services/BotStandaloneService.cs b/Botticelli.Framework/Services/BotStandaloneService.cs new file mode 100644 index 00000000..2882e9b2 --- /dev/null +++ b/Botticelli.Framework/Services/BotStandaloneService.cs @@ -0,0 +1,26 @@ +using Botticelli.Interfaces; +using Botticelli.Shared.API.Admin.Requests; +using Microsoft.Extensions.Logging; + +namespace Botticelli.Framework.Services; + +public class BotStandaloneService( + IHttpClientFactory httpClientFactory, + BotData.Entities.Bot.BotData botData, + IBot bot, + ILogger logger) + : BotActualizationService(httpClientFactory, + bot, + logger) +{ + public override async Task StartAsync(CancellationToken cancellationToken) + { + await Bot.SetBotContext(botData, cancellationToken); + await Bot.StartBotAsync(StartBotRequest.GetInstance(), cancellationToken); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + await Bot.StopBotAsync(StopBotRequest.GetInstance(), cancellationToken); + } +} \ No newline at end of file diff --git a/Botticelli.Framework/Services/BotStatusService.cs b/Botticelli.Framework/Services/BotStatusService.cs index 08635e9a..1b703c20 100644 --- a/Botticelli.Framework/Services/BotStatusService.cs +++ b/Botticelli.Framework/Services/BotStatusService.cs @@ -17,11 +17,12 @@ public class BotStatusService( IBot bot, ILogger logger) : BotActualizationService(httpClientFactory, - serverSettings, bot, - logger) + logger, + serverSettings) { - private const short GetStatusPeriod = 5000; + private const short GetStatusPeriod = 10; + private const short MaxGetStatusPeriod = 120; private Task? _getRequiredStatusEventTask; public override Task StartAsync(CancellationToken cancellationToken) @@ -32,7 +33,7 @@ public override Task StartAsync(CancellationToken cancellationToken) } /// - /// Get required status for a bot from server + /// Get the required status for a bot from server /// /// /// @@ -48,11 +49,21 @@ private void GetRequiredStatus(CancellationToken cancellationToken) }; _getRequiredStatusEventTask = Policy.HandleResult(_ => true) - .WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(GetStatusPeriod)) - .ExecuteAndCaptureAsync(ct => Process(request, ct)!, - cancellationToken); + .WaitAndRetryForeverAsync((_, ctx) => !ctx.TryGetValue("delay", out var delay) ? TimeSpan.FromSeconds(GetStatusPeriod) : (TimeSpan)delay, (result, i, timeSpan, context) => + { + var gotRetries = context.TryGetValue("gotRetries", out var gr) ? (int)gr + 1 : 0; + var delay = GetDelay(gotRetries); + + context["gotRetries"] = result.Result.Status == BotStatus.Error ? gotRetries : 0; + context["delay"] = result.Result.Status == BotStatus.Error ? + TimeSpan.FromSeconds(delay > MaxGetStatusPeriod ? MaxGetStatusPeriod : delay) : + TimeSpan.FromSeconds(GetStatusPeriod); + }) + .ExecuteAndCaptureAsync(ct => Process(request, ct)!, cancellationToken); } + private static double GetDelay(int i) => GetStatusPeriod + GetStatusPeriod * Math.Log(i + 1, Math.E); + private Task Process(GetRequiredStatusFromServerRequest request, CancellationToken cancellationToken) { @@ -64,11 +75,31 @@ private void GetRequiredStatus(CancellationToken cancellationToken) var taskResult = task.Result; - if (taskResult == null) throw new BotException("No result from server!"); + if (taskResult == null) + { + logger.LogError("No result from server!"); + + return Task.FromResult(new GetRequiredStatusFromServerResponse + { + Status = BotStatus.Error, + BotId = BotId ?? string.Empty, + BotContext = null + }); + } var botContext = taskResult.BotContext; - if (botContext == null) throw new BotException("No bot context from server!"); + if (botContext == null) + { + logger.LogError("No bot context from server!"); + + return Task.FromResult(new GetRequiredStatusFromServerResponse + { + Status = BotStatus.Error, + BotId = BotId ?? string.Empty, + BotContext = null + }); + } var botData = new BotData.Entities.Bot.BotData { @@ -77,7 +108,7 @@ private void GetRequiredStatus(CancellationToken cancellationToken) BotKey = botContext.BotKey, AdditionalInfo = botContext.Items?.Select(it => new BotAdditionalInfo { - BotId = taskResult!.BotId, + BotId = taskResult.BotId, ItemName = it.Key, ItemValue = it.Value }) @@ -88,7 +119,7 @@ private void GetRequiredStatus(CancellationToken cancellationToken) if (task.Exception != null) { - Logger.LogError($"GetRequiredStatus task error: {task.Exception?.Message}"); + Logger.LogError("GetRequiredStatus task error: {Message}", task.Exception?.Message); Bot.StopBotAsync(StopBotRequest.GetInstance(), cancellationToken); return task; @@ -102,6 +133,7 @@ private void GetRequiredStatus(CancellationToken cancellationToken) break; case BotStatus.Locked: case BotStatus.Unknown: + case BotStatus.Error: case null: Bot.StopBotAsync(StopBotRequest.GetInstance(), cancellationToken); diff --git a/Botticelli.Framework/Services/MarkAsReceivedService.cs b/Botticelli.Framework/Services/MarkAsReceivedService.cs deleted file mode 100644 index 46e8b0fb..00000000 --- a/Botticelli.Framework/Services/MarkAsReceivedService.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Botticelli.Framework.Options; -using Botticelli.Interfaces; -using Botticelli.Shared.API.Client.Requests; -using Botticelli.Shared.API.Client.Responses; -using Microsoft.Extensions.Logging; - -namespace Botticelli.Framework.Services; - -/// -/// Marks a broadcast message as received -/// -/// -/// -/// -/// -/// -public class MarkAsReceivedService( - IHttpClientFactory httpClientFactory, - ServerSettings serverSettings, - IBot bot, - ILogger logger) - : PollActualizationService(httpClientFactory, - "broadcast", - serverSettings, - bot, - logger) -{ - private readonly IBot _bot = bot; - - protected override async Task InnerProcess(MarksAsReceivedResponse response, CancellationToken ct) - { - } -} \ No newline at end of file diff --git a/Botticelli.Framework/Services/PollActualizationService.cs b/Botticelli.Framework/Services/PollActualizationService.cs index 8cdca0d0..a05bba66 100644 --- a/Botticelli.Framework/Services/PollActualizationService.cs +++ b/Botticelli.Framework/Services/PollActualizationService.cs @@ -16,9 +16,9 @@ public class PollActualizationService( IBot bot, ILogger logger) : BotActualizationService(httpClientFactory, - serverSettings, bot, - logger) + logger, + serverSettings) where TRequest : IBotRequest, new() { private const short ActionPeriod = 5000; diff --git a/Botticelli.Interfaces/Botticelli.Interfaces.csproj b/Botticelli.Interfaces/Botticelli.Interfaces.csproj index 9f42f3c0..e47b9d40 100644 --- a/Botticelli.Interfaces/Botticelli.Interfaces.csproj +++ b/Botticelli.Interfaces/Botticelli.Interfaces.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli diff --git a/Botticelli.Interfaces/IEventBasedBotClientApi.cs b/Botticelli.Interfaces/IEventBasedBotClientApi.cs index accf23e6..ccdc58c3 100644 --- a/Botticelli.Interfaces/IEventBasedBotClientApi.cs +++ b/Botticelli.Interfaces/IEventBasedBotClientApi.cs @@ -45,5 +45,5 @@ public Task UpdateMessageAsync(SendMessageReq CancellationToken token) where TSendOptions : class; - public Task DeleteMessageAsync(RemoveMessageRequest request, CancellationToken token); + public Task DeleteMessageAsync(DeleteMessageRequest request, CancellationToken token); } \ No newline at end of file diff --git a/Botticelli.Locations.Telegram/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Locations.Telegram/Extensions/ServiceCollectionExtensions.cs index 21cdc550..042073b0 100644 --- a/Botticelli.Locations.Telegram/Extensions/ServiceCollectionExtensions.cs +++ b/Botticelli.Locations.Telegram/Extensions/ServiceCollectionExtensions.cs @@ -1,6 +1,6 @@ using System.Reflection; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Framework.Telegram.Layout; using Botticelli.Locations.Commands; using Botticelli.Locations.Commands.CommandProcessors; @@ -41,11 +41,9 @@ public static IServiceCollection AddOsmLocations(this IServiceCollection service .AddScoped() .AddScoped, InlineTelegramLayoutSupplier>() .AddScoped, ReplyTelegramLayoutSupplier>() - .AddScoped(sp => - new ForwardGeocoder(sp.GetRequiredService(), - Url.Combine(url, "search"))) - .AddScoped(sp => - new ReverseGeocoder(sp.GetRequiredService(), - Url.Combine(url, "reverse"))); + .AddScoped(sp => new ForwardGeocoder(sp.GetRequiredService(), + Url.Combine(url, "search"))) + .AddScoped(sp => new ReverseGeocoder(sp.GetRequiredService(), + Url.Combine(url, "reverse"))); } } \ No newline at end of file diff --git a/Botticelli.Locations.Tests/Botticelli.Locations.Tests.csproj b/Botticelli.Locations.Tests/Botticelli.Locations.Tests.csproj index 0d1efa6d..2917f24b 100644 --- a/Botticelli.Locations.Tests/Botticelli.Locations.Tests.csproj +++ b/Botticelli.Locations.Tests/Botticelli.Locations.Tests.csproj @@ -20,7 +20,7 @@ - + \ No newline at end of file diff --git a/Botticelli.Locations.Tests/ForwardGeocoderMock.cs b/Botticelli.Locations.Tests/ForwardGeocoderMock.cs index c95102a1..4db49924 100644 --- a/Botticelli.Locations.Tests/ForwardGeocoderMock.cs +++ b/Botticelli.Locations.Tests/ForwardGeocoderMock.cs @@ -8,8 +8,8 @@ public class ForwardGeocoderMock : IForwardGeocoder { public async Task Geocode(ForwardGeocodeRequest req) { - return new GeocodeResponse[] - { + return + [ new() { OSMID = 110, @@ -32,6 +32,6 @@ public async Task Geocode(ForwardGeocodeRequest req) }, GeoText = "Test Result" } - }; + ]; } } \ No newline at end of file diff --git a/Botticelli.Locations.Tests/ReverseGeocoderMock.cs b/Botticelli.Locations.Tests/ReverseGeocoderMock.cs index c11e9f2c..c4a66771 100644 --- a/Botticelli.Locations.Tests/ReverseGeocoderMock.cs +++ b/Botticelli.Locations.Tests/ReverseGeocoderMock.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Nominatim.API.Interfaces; using Nominatim.API.Models; @@ -8,8 +9,8 @@ public class ReverseGeocoderMock : IReverseGeocoder { public Task ReverseGeocode(ReverseGeocodeRequest req) { - if (req is { Latitude: not null, Longitude: not null }) - return Task.FromResult(new() + if (req is {Latitude: not null, Longitude: not null}) + return Task.FromResult(new GeocodeResponse { Latitude = req.Latitude.Value, Longitude = req.Longitude.Value, @@ -29,7 +30,7 @@ public Task ReverseGeocode(ReverseGeocodeRequest req) Name = string.Empty } }); - - throw new System.InvalidOperationException(); + + throw new InvalidOperationException(); } } \ No newline at end of file diff --git a/Botticelli.Locations.Vk/Botticelli.Locations.Vk.csproj b/Botticelli.Locations.Vk/Botticelli.Locations.Vk.csproj deleted file mode 100644 index ad045dd9..00000000 --- a/Botticelli.Locations.Vk/Botticelli.Locations.Vk.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net8.0 - enable - enable - Botticelli.Locations.Vk1 - - - - - ..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.4\Microsoft.Extensions.Configuration.Abstractions.dll - - - ..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.4\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - - - - - - - - - - - diff --git a/Botticelli.Locations.Vk/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Locations.Vk/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index b4569ba2..00000000 --- a/Botticelli.Locations.Vk/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Reflection; -using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; -using Botticelli.Framework.Vk.Messages.API.Markups; -using Botticelli.Framework.Vk.Messages.Layout; -using Botticelli.Locations.Commands; -using Botticelli.Locations.Commands.CommandProcessors; -using Botticelli.Locations.Integration; -using Botticelli.Locations.Options; -using Flurl; -using Mapster; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Nominatim.API.Address; -using Nominatim.API.Geocoders; -using Nominatim.API.Interfaces; -using Nominatim.API.Web; - -namespace Botticelli.Locations.Vk.Extensions; - -public static class ServiceCollectionExtensions -{ - /// - /// Adds an OSM location provider - /// - /// - public static IServiceCollection AddOsmLocations(this IServiceCollection services, - IConfiguration config, - string url = "https://nominatim.openstreetmap.org") - { - services.AddHttpClient(); - TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly()); - - return services.Configure(config) - .AddScoped, PassValidator>() - .AddScoped>() - .AddScoped>() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped, VkLayoutSupplier>() - .AddScoped(sp => - new ForwardGeocoder(sp.GetRequiredService(), - Url.Combine(url, "search"))) - .AddScoped(sp => - new ReverseGeocoder(sp.GetRequiredService(), - Url.Combine(url, "reverse"))); - } -} \ No newline at end of file diff --git a/Botticelli.Locations/Botticelli.Locations.csproj b/Botticelli.Locations/Botticelli.Locations.csproj index d2fd7909..dc7225e1 100644 --- a/Botticelli.Locations/Botticelli.Locations.csproj +++ b/Botticelli.Locations/Botticelli.Locations.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/Botticelli.Locations/Commands/CommandProcessors/FindLocationsCommandProcessor.cs b/Botticelli.Locations/Commands/CommandProcessors/FindLocationsCommandProcessor.cs index 9a78db31..751725df 100644 --- a/Botticelli.Locations/Commands/CommandProcessors/FindLocationsCommandProcessor.cs +++ b/Botticelli.Locations/Commands/CommandProcessors/FindLocationsCommandProcessor.cs @@ -1,10 +1,10 @@ using Botticelli.Client.Analytics; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.BasicControls; -using Botticelli.Framework.Controls.Layouts; -using Botticelli.Framework.Controls.Layouts.Inlines; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.BasicControls; +using Botticelli.Controls.Layouts; +using Botticelli.Controls.Layouts.Inlines; +using Botticelli.Controls.Parsers; using Botticelli.Framework.SendOptions; using Botticelli.Locations.Integration; using Botticelli.Shared.API.Client.Requests; @@ -23,13 +23,13 @@ public class FindLocationsCommandProcessor( IValidator messageValidator) : CommandProcessor(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) where TReplyMarkup : class { protected override async Task InnerProcess(Message message, CancellationToken token) { - var query = string.Join(" ", message.Body?.Split(" ").Skip(1) ?? Array.Empty()); + var query = string.Join(" ", message.Body?.Split(" ").Skip(1) ?? []); var results = await locationProvider.Search(query, 10); @@ -63,6 +63,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot.SendMessageAsync(request, replyOptions, token); + await SendMessage(request, replyOptions, token); } } \ No newline at end of file diff --git a/Botticelli.Locations/Commands/CommandProcessors/MapCommandProcessor.cs b/Botticelli.Locations/Commands/CommandProcessors/MapCommandProcessor.cs index 292a1b29..86effb00 100644 --- a/Botticelli.Locations/Commands/CommandProcessors/MapCommandProcessor.cs +++ b/Botticelli.Locations/Commands/CommandProcessors/MapCommandProcessor.cs @@ -18,8 +18,8 @@ public MapCommandProcessor(ILogger> logger, IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { } @@ -35,6 +35,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot.SendMessageAsync(request, token); + await SendMessage(request, token); } } \ No newline at end of file diff --git a/Botticelli.Pay.Telegram/Extensions/ServiceCollectionExtensions.cs b/Botticelli.Pay.Telegram/Extensions/ServiceCollectionExtensions.cs index 1c85d9c6..4ba6e31f 100644 --- a/Botticelli.Pay.Telegram/Extensions/ServiceCollectionExtensions.cs +++ b/Botticelli.Pay.Telegram/Extensions/ServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ using Botticelli.Bot.Data.Settings; using Botticelli.Client.Analytics.Settings; using Botticelli.Framework.Options; +using Botticelli.Framework.Telegram.Builders; using Botticelli.Framework.Telegram.Extensions; using Botticelli.Framework.Telegram.Options; using Botticelli.Pay.Extensions; @@ -24,8 +25,8 @@ public static class ServiceCollectionExtensions /// /// /// - public static IServiceCollection AddTelegramPayBot(this IServiceCollection services, - Action> optionsBuilderFunc, + public static TelegramBotBuilder> AddTelegramPayBot(this IServiceCollection services, + Action> optionsBuilderFunc, Action> analyticsOptionsBuilderFunc, Action> serverSettingsBuilderFunc, Action> dataAccessSettingsBuilderFunc) @@ -34,12 +35,13 @@ public static IServiceCollection AddTelegramPayBot { services.AddPayments(); - return services.AddTelegramBot(optionsBuilderFunc, + return services.AddTelegramBot>(optionsBuilderFunc, analyticsOptionsBuilderFunc, serverSettingsBuilderFunc, dataAccessSettingsBuilderFunc, - o => o.AddSubHandler() - .AddSubHandler()); + o => o + .AddSubHandler() + .AddSubHandler()); } /// @@ -48,14 +50,33 @@ public static IServiceCollection AddTelegramPayBot /// /// /// - public static IServiceCollection AddTelegramPayBot(this IServiceCollection services, IConfiguration configuration) + public static TelegramBotBuilder> AddTelegramPayBot(this IServiceCollection services, IConfiguration configuration) where THandler : IPreCheckoutHandler, new() where TProcessor : IPayProcessor { services.AddPayments(); - return services.AddTelegramBot(configuration, + return services.AddTelegramBot>(configuration, o => o.AddSubHandler() .AddSubHandler()); } + + /// + /// Adds a standalone Telegram bot with a payment function + /// + /// + /// + /// + public static TelegramStandaloneBotBuilder AddStandaloneTelegramPayBot(this IServiceCollection services, + IConfiguration configuration) + where THandler : IPreCheckoutHandler, new() + where TProcessor : IPayProcessor + { + services.AddPayments(); + + return services.AddStandaloneTelegramBot(configuration, + o => o + .AddSubHandler() + .AddSubHandler()); + } } \ No newline at end of file diff --git a/Botticelli.Pay.Telegram/TelegramPaymentBot.cs b/Botticelli.Pay.Telegram/TelegramPaymentBot.cs index f8b1c494..860bf183 100644 --- a/Botticelli.Pay.Telegram/TelegramPaymentBot.cs +++ b/Botticelli.Pay.Telegram/TelegramPaymentBot.cs @@ -23,9 +23,9 @@ public TelegramPaymentBot(ITelegramBotClient client, IBotDataAccess data) : base(client, handler, logger, - metrics, textTransformer, - data) + data, + metrics) { } @@ -35,12 +35,6 @@ protected override async Task AdditionalProcessing(SendMessageRequ string chatId, CancellationToken token) { - await base.AdditionalProcessing(request, - optionsBuilder, - isUpdate, - chatId, - token); - var invoice = (request.Message as PayInvoiceMessage)?.Invoice; if (invoice is not null) @@ -56,7 +50,7 @@ await Client.SendInvoice(chatId, cancellationToken: token); } - private int ConvertPrice(decimal price, Currency currency) + private static int ConvertPrice(decimal price, Currency currency) { return Convert.ToInt32(price * (decimal) Math.Pow(10, currency.Decimals ?? 2)); } diff --git a/Botticelli.Pay/Botticelli.Pay.csproj b/Botticelli.Pay/Botticelli.Pay.csproj index e9cb48e8..ead78eb7 100644 --- a/Botticelli.Pay/Botticelli.Pay.csproj +++ b/Botticelli.Pay/Botticelli.Pay.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli @@ -13,7 +13,7 @@ True - + diff --git a/Botticelli.Scheduler/Botticelli.Scheduler.csproj b/Botticelli.Scheduler/Botticelli.Scheduler.csproj index 4bc9c37f..a49f32d4 100644 --- a/Botticelli.Scheduler/Botticelli.Scheduler.csproj +++ b/Botticelli.Scheduler/Botticelli.Scheduler.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli.Scheduler BotticelliBots new_logo_compact.png diff --git a/Botticelli.Server.Analytics/Botticelli.Server.Analytics.csproj b/Botticelli.Server.Analytics/Botticelli.Server.Analytics.csproj index ae657a1c..ea1124cf 100644 --- a/Botticelli.Server.Analytics/Botticelli.Server.Analytics.csproj +++ b/Botticelli.Server.Analytics/Botticelli.Server.Analytics.csproj @@ -13,7 +13,7 @@ enable enable true - 0.7.0 + 0.8.0 https://botticellibots.com new_logo_compact.png https://github.com/devgopher/botticelli @@ -47,16 +47,16 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/Botticelli.Server.Analytics/Program.cs b/Botticelli.Server.Analytics/Program.cs index 21245598..34eb635f 100644 --- a/Botticelli.Server.Analytics/Program.cs +++ b/Botticelli.Server.Analytics/Program.cs @@ -30,7 +30,7 @@ Id = "Bearer" } }, - Array.Empty() + [] } }); }); diff --git a/Botticelli.Server.Back/Botticelli.Server.Back.csproj b/Botticelli.Server.Back/Botticelli.Server.Back.csproj index 4abafdf0..ce935b8f 100644 --- a/Botticelli.Server.Back/Botticelli.Server.Back.csproj +++ b/Botticelli.Server.Back/Botticelli.Server.Back.csproj @@ -8,7 +8,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli BotticelliBots https://botticellibots.com @@ -63,13 +63,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Botticelli.Server.Back/Controllers/AdminController.cs b/Botticelli.Server.Back/Controllers/AdminController.cs index 8234ed7e..ed1b03a5 100644 --- a/Botticelli.Server.Back/Controllers/AdminController.cs +++ b/Botticelli.Server.Back/Controllers/AdminController.cs @@ -1,10 +1,13 @@ -using Botticelli.Server.Back.Services; +using System.Text; +using System.Text.Json; +using Botticelli.Server.Back.Services; using Botticelli.Server.Back.Services.Broadcasting; using Botticelli.Server.Data.Entities.Bot; using Botticelli.Server.Data.Entities.Bot.Broadcasting; using Botticelli.Shared.API.Admin.Responses; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.API.Client.Responses; +using Botticelli.Shared.ValueObjects; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -16,34 +19,30 @@ namespace Botticelli.Server.Back.Controllers; [ApiController] [Authorize(AuthenticationSchemes = "Bearer")] [Route("/v1/admin")] -public class AdminController +public class AdminController( + IBotManagementService botManagementService, + IBotStatusDataService botStatusDataService, + ILogger logger, + IBroadcastService broadcastService) : ControllerBase { - private readonly IBotManagementService _botManagementService; - private readonly IBotStatusDataService _botStatusDataService; - private readonly IBroadcastService _broadcastService; - private readonly ILogger _logger; - - public AdminController(IBotManagementService botManagementService, - IBotStatusDataService botStatusDataService, - ILogger logger, - IBroadcastService broadcastService) - { - _botManagementService = botManagementService; - _botStatusDataService = botStatusDataService; - _logger = logger; - _broadcastService = broadcastService; - } - + /// + /// Adds a new bot + /// + /// + /// [HttpPost("[action]")] public async Task AddNewBot([FromBody] RegisterBotRequest request) { - _logger.LogInformation($"{nameof(AddNewBot)}({request.BotId}) started..."); - var success = await _botManagementService.RegisterBot(request.BotId, - request.BotKey, - request.BotName, - request.Type); + logger.LogInformation("{AddNewBotName}({RequestBotId}) started...", nameof(AddNewBot), request.BotId); + var success = await botManagementService.RegisterBot(request.BotId, + request.BotKey, + request.BotName, + request.Type); - _logger.LogInformation($"{nameof(AddNewBot)}({request.BotId}) success: {success}..."); + logger.LogInformation("{AddNewBotName}({RequestBotId}) success: {Success}...", + nameof(AddNewBot), + request.BotId, + success); return new RegisterBotResponse { @@ -52,15 +51,23 @@ public async Task AddNewBot([FromBody] RegisterBotRequest r }; } + /// + /// Updates a bot + /// + /// + /// [HttpPut("[action]")] public async Task UpdateBot([FromBody] UpdateBotRequest request) { - _logger.LogInformation($"{nameof(UpdateBot)}({request.BotId}) started..."); - var success = await _botManagementService.UpdateBot(request.BotId, - request.BotKey, - request.BotName); + logger.LogInformation("{UpdateBotName}({RequestBotId}) started...", nameof(UpdateBot), request.BotId); + var success = await botManagementService.UpdateBot(request.BotId, + request.BotKey, + request.BotName); - _logger.LogInformation($"{nameof(UpdateBot)}({request.BotId}) success: {success}..."); + logger.LogInformation("{UpdateBotName}({RequestBotId}) success: {Success}...", + nameof(UpdateBot), + request.BotId, + success); return new UpdateBotResponse { @@ -75,38 +82,94 @@ public async Task UpdateBot([FromBody] UpdateBotRequest reque /// /// /// - [HttpGet("[action]")] - public async Task SendBroadcast([FromQuery] string botId, [FromQuery] string message) + [HttpPost("[action]")] + public async Task SendBroadcast([FromQuery] string botId, [FromBody] Message message) + { + await DoBroadcast(botId, message); + } + + /// + /// Sends a broadcast message in binary stream + /// + /// + /// + /// + [HttpPost("[action]")] + public async Task SendBroadcastBinary([FromQuery] string botId) { - await _broadcastService.BroadcastMessage(new Broadcast + using var memoryStream = new MemoryStream(); + await Request.Body.CopyToAsync(memoryStream); + + var message = JsonSerializer.Deserialize(Encoding.UTF8.GetString(memoryStream.ToArray())); + + await DoBroadcast(botId, message); + } + + private async Task DoBroadcast(string botId, Message? message) + { + await broadcastService.BroadcastMessage(new Broadcast { - Id = Guid.NewGuid().ToString(), - BotId = botId, - Body = message + Id = message.Uid ?? throw new NullReferenceException("Id cannot be null!"), + BotId = botId ?? throw new NullReferenceException("BotId cannot be null!"), + Body = message.Body ?? throw new NullReferenceException("Body cannot be null!"), + Attachments = message.Attachments + .Where(a => a is BinaryBaseAttachment) + .Select(a => + { + if (a is BinaryBaseAttachment baseAttachment) + return new BroadcastAttachment + { + Id = Guid.NewGuid(), + BroadcastId = message.Uid, + MediaType = baseAttachment.MediaType, + Filename = baseAttachment.Name, + Content = baseAttachment.Data + }; + + return null!; + }) + .ToList(), + Sent = true }); } + /// + /// Get bots list + /// + /// [HttpGet("[action]")] public Task> GetBots() { - return Task.FromResult(_botStatusDataService.GetBots()); + return Task.FromResult(botStatusDataService.GetBots()); } + /// + /// Activates a bot (BotStatus.Unlocked) + /// + /// [HttpGet("[action]")] public async Task ActivateBot([FromQuery] string botId) { - await _botManagementService.SetRequiredBotStatus(botId, BotStatus.Unlocked); + await botManagementService.SetRequiredBotStatus(botId, BotStatus.Unlocked); } + /// + /// Deactivates a bot (BotStatus.Locked) + /// + /// [HttpGet("[action]")] public async Task DeactivateBot([FromQuery] string botId) { - await _botManagementService.SetRequiredBotStatus(botId, BotStatus.Locked); + await botManagementService.SetRequiredBotStatus(botId, BotStatus.Locked); } + /// + /// Removes a bot + /// + /// [HttpGet("[action]")] public async Task RemoveBot([FromQuery] string botId) { - await _botManagementService.RemoveBot(botId); + await botManagementService.RemoveBot(botId); } } \ No newline at end of file diff --git a/Botticelli.Server.Back/Controllers/AuthController.cs b/Botticelli.Server.Back/Controllers/AuthController.cs index 0fd4fe67..6c755f53 100644 --- a/Botticelli.Server.Back/Controllers/AuthController.cs +++ b/Botticelli.Server.Back/Controllers/AuthController.cs @@ -11,19 +11,17 @@ namespace Botticelli.Server.Back.Controllers; [ApiController] [AllowAnonymous] [Route("/v1/auth")] -public class AuthController +public class AuthController(IAdminAuthService adminAuthService) { - private readonly IAdminAuthService _adminAuthService; - - public AuthController(IAdminAuthService adminAuthService) - { - _adminAuthService = adminAuthService; - } - + /// + /// Gets auth token for a user + /// + /// + /// [AllowAnonymous] [HttpPost("[action]")] public IActionResult GetToken(UserLoginRequest request) { - return new OkObjectResult(_adminAuthService.GenerateToken(request)); + return new OkObjectResult(adminAuthService.GenerateToken(request)); } } \ No newline at end of file diff --git a/Botticelli.Server.Back/Controllers/BotController.cs b/Botticelli.Server.Back/Controllers/BotController.cs index 4fec7d3e..5028a8c3 100644 --- a/Botticelli.Server.Back/Controllers/BotController.cs +++ b/Botticelli.Server.Back/Controllers/BotController.cs @@ -12,7 +12,7 @@ namespace Botticelli.Server.Back.Controllers; /// -/// Bot status controller +/// Bot status/data controller /// [ApiController] [AllowAnonymous] @@ -23,6 +23,9 @@ public class BotController( IBroadcastService broadcastService, ILogger logger) { + private const int LongPollTimeoutSeconds = 30; + private const int DefaultPollIntervalMilliseconds = 300; + #region Client pane /// @@ -40,13 +43,12 @@ public async Task GetRequiredBotStatus([Fro var botInfo = await botStatusDataService.GetBotInfo(request.BotId!); botInfo.NotNull(); botInfo?.BotKey?.NotNullOrEmpty(); - botInfo!.AdditionalInfo!.NotNullOrEmpty(); var context = new BotContext { BotId = botInfo!.BotId, BotKey = botInfo.BotKey!, - Items = botInfo.AdditionalInfo!.ToDictionary(k => k.ItemName, k => k.ItemValue)! + Items = botInfo.AdditionalInfo?.ToDictionary(k => k.ItemName, k => k.ItemValue)! }; return new GetRequiredStatusFromServerResponse @@ -69,7 +71,7 @@ public async Task KeepAlive([FromBody] KeepAliveN { try { - logger.LogTrace($"{nameof(KeepAlive)}({request.BotId})..."); + logger.LogTrace("{KeepAliveName}({RequestBotId})...", nameof(KeepAlive), request.BotId); request.BotId?.NotNullOrEmpty(); await botManagementService.SetKeepAlive(request.BotId!); @@ -81,7 +83,7 @@ public async Task KeepAlive([FromBody] KeepAliveN } catch (Exception ex) { - logger.LogError(ex, $"{nameof(KeepAlive)}({request.BotId}) error: {ex.Message}"); + logger.LogError(ex, "{KeepAliveName}({RequestBotId}) error: {ExMessage}", nameof(KeepAlive), request.BotId, ex.Message); return new KeepAliveNotificationResponse { @@ -98,38 +100,46 @@ public async Task KeepAlive([FromBody] KeepAliveN /// [AllowAnonymous] [HttpPost("client/[action]")] - public async Task Broadcast([FromBody] GetBroadCastMessagesRequest request) + public async Task GetBroadcast([FromBody] GetBroadCastMessagesRequest request) { try { - logger.LogTrace($"{nameof(Broadcast)}({request.BotId})..."); request.BotId?.NotNullOrEmpty(); - var broadcastMessages = await broadcastService.GetMessages(request.BotId!); - + var broadcastMessages = new List(); + var started = DateTime.UtcNow; + + while (!broadcastMessages.Any() && DateTime.UtcNow.Subtract(started).TotalSeconds < LongPollTimeoutSeconds) + { + broadcastMessages = (await broadcastService.GetMessages(request.BotId!)).ToList(); + + await Task.Delay(DefaultPollIntervalMilliseconds); + } + return new GetBroadCastMessagesResponse { BotId = request.BotId!, IsSuccess = true, Messages = broadcastMessages.Select(bm => new Message { + Uid = bm.Id, Type = Message.MessageType.Messaging, Subject = string.Empty, Body = bm.Body, - Attachments = bm.Attachments?.Select(a => - new BinaryBaseAttachment(Guid.NewGuid().ToString(), - a.Filename, - (MediaType) a.MediaType, - string.Empty, - a.Content)) - .ToList() + Attachments = bm.Attachments?.Select(BaseAttachment (att) => new BinaryBaseAttachment(att.Id.ToString(), + att.Filename, + att.MediaType, + string.Empty, + att.Content)) + .ToList() ?? + [] }) .ToArray() }; } catch (Exception ex) { - logger.LogError(ex, $"{nameof(Broadcast)}({request.BotId}) error: {ex.Message}"); + logger.LogError(ex, "{GetBroadcastName}({RequestBotId}) error: {ExMessage}", nameof(GetBroadcast), request.BotId, ex.Message); return new GetBroadCastMessagesResponse { @@ -151,7 +161,7 @@ public async Task BroadcastReceived([FromBody { try { - logger.LogTrace($"{nameof(Broadcast)}({request.BotId})..."); + logger.LogTrace("{GetBroadcastName}({RequestBotId})...", nameof(GetBroadcast), request.BotId); request.BotId?.NotNullOrEmpty(); foreach (var messageId in request.MessageIds) await broadcastService.MarkReceived(request.BotId!, messageId); @@ -163,7 +173,7 @@ public async Task BroadcastReceived([FromBody } catch (Exception ex) { - logger.LogError(ex, $"{nameof(BroadcastReceived)}({request.BotId}) error: {ex.Message}"); + logger.LogError(ex, "{BroadcastReceivedName}({RequestBotId}) error: {ExMessage}", nameof(BroadcastReceived), request.BotId, ex.Message); return new BroadCastMessagesReceivedResponse { diff --git a/Botticelli.Server.Back/Controllers/UserController.cs b/Botticelli.Server.Back/Controllers/UserController.cs index f99978a1..5e374f42 100644 --- a/Botticelli.Server.Back/Controllers/UserController.cs +++ b/Botticelli.Server.Back/Controllers/UserController.cs @@ -1,46 +1,48 @@ using Botticelli.Server.Back.Services.Auth; +using Botticelli.Server.Back.Settings; using Botticelli.Server.Data.Entities.Auth; using Botticelli.Shared.Utils; using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using PasswordGenerator; namespace Botticelli.Server.Back.Controllers; /// -/// Admin controller getting/adding/removing bots +/// Controller for user authorization on admin part /// [ApiController] [Authorize(AuthenticationSchemes = "Bearer")] [Route("/v1/user")] -public class UserController : Controller +public class UserController(IUserService userService, IMapper mapper, IPasswordSender passwordSender, IOptionsMonitor settings) : Controller { - private readonly IMapper _mapper; - private readonly IPassword _password = new Password(true, true, true, false, - 12); - - private readonly IPasswordSender _passwordSender; - private readonly IUserService _userService; - - public UserController(IUserService userService, IMapper mapper, IPasswordSender passwordSender) - { - _userService = userService; - _mapper = mapper; - _passwordSender = passwordSender; - } - + Random.Shared.Next(settings.CurrentValue.PasswordMinLength, + settings.CurrentValue.PasswordMaxLength)); + + /// + /// Does system contain any users? + /// + /// + /// [HttpGet("[action]")] [AllowAnonymous] public async Task HasUsersAsync(CancellationToken token) { - return Ok(await _userService.HasUsers(token)); + return Ok(await userService.HasUsers(token)); } + /// + /// Adds a default user + /// + /// + /// + /// [HttpPost("[action]")] [AllowAnonymous] public async Task AddDefaultUserAsync(DefaultUserAddRequest request, CancellationToken token) @@ -52,10 +54,10 @@ public async Task AddDefaultUserAsync(DefaultUserAddRequest reque request.Email.NotNull(); var password = _password.Next(); - var mapped = _mapper.Map(request); + var mapped = mapper.Map(request); mapped.Password = password; - if (await _userService.CheckAndAddAsync(mapped, token)) await _passwordSender.SendPassword(request.Email!, password, token); + if (await userService.CheckAndAddAsync(mapped, token)) await passwordSender.SendPassword(request.Email!, password, token); } catch (Exception ex) { @@ -65,6 +67,12 @@ public async Task AddDefaultUserAsync(DefaultUserAddRequest reque return Ok(); } + /// + /// Regenerates passsword + /// + /// + /// + /// [HttpPost("[action]")] [AllowAnonymous] public async Task RegeneratePasswordAsync(RegeneratePasswordRequest passwordRequest, @@ -76,11 +84,11 @@ public async Task RegeneratePasswordAsync(RegeneratePasswordReque passwordRequest.UserName.NotNull(); passwordRequest.Email.NotNull(); - var mapped = _mapper.Map(passwordRequest); + var mapped = mapper.Map(passwordRequest); mapped.Password = _password.Next(); - await _userService.UpdateAsync(mapped, token); - await _passwordSender.SendPassword(passwordRequest.Email!, mapped.Password, token); + await userService.UpdateAsync(mapped, token); + await passwordSender.SendPassword(passwordRequest.Email!, mapped.Password, token); } catch (Exception ex) { @@ -90,13 +98,19 @@ public async Task RegeneratePasswordAsync(RegeneratePasswordReque return Ok(); } + /// + /// Adds a new user + /// + /// + /// + /// [HttpPost] [AllowAnonymous] public async Task AddUserAsync(UserAddRequest request, CancellationToken token) { try { - await _userService.AddAsync(request, true, token); + await userService.AddAsync(request, true, token); } catch (Exception ex) { @@ -107,6 +121,11 @@ public async Task AddUserAsync(UserAddRequest request, Cancellati } + /// + /// Gets an authorized current user + /// + /// + /// [HttpGet("[action]")] public async Task> GetCurrentUserAsync(CancellationToken token) { @@ -118,19 +137,25 @@ public async Task> GetCurrentUserAsync(Cancellatio return await GetUserAsync(request, token); } - + private string GetCurrentUserName() { return HttpContext.User.Claims.FirstOrDefault(c => c.Type == "applicationUserName")?.Value ?? throw new NullReferenceException(); } + /// + /// Gets an information about a user + /// + /// + /// + /// [HttpGet] public async Task> GetUserAsync(UserGetRequest request, CancellationToken token) { try { - return new ActionResult(await _userService.GetAsync(request, token)); + return new ActionResult(await userService.GetAsync(request, token)); } catch (Exception ex) { @@ -138,6 +163,12 @@ public async Task> GetUserAsync(UserGetRequest req } } + /// + /// Updates a current user + /// + /// + /// + /// [HttpPut("[action]")] public async Task UpdateCurrentUserAsync(UserUpdateRequest request, CancellationToken token) { @@ -147,12 +178,18 @@ public async Task UpdateCurrentUserAsync(UserUpdateRequest reques return await UpdateUserAsync(request, token); } + /// + /// Updates a given user + /// + /// + /// + /// [HttpPut] public async Task UpdateUserAsync(UserUpdateRequest request, CancellationToken token) { try { - await _userService.UpdateAsync(request, token); + await userService.UpdateAsync(request, token); return Ok(); } @@ -162,6 +199,12 @@ public async Task UpdateUserAsync(UserUpdateRequest request, Canc } } + /// + /// Deletes a user + /// + /// + /// + /// [HttpDelete] public async Task DeleteUserAsync(UserDeleteRequest request, CancellationToken token) { @@ -171,7 +214,7 @@ public async Task DeleteUserAsync(UserDeleteRequest request, Canc if (request.UserName == user) return BadRequest("You can't delete yourself!"); - await _userService.DeleteAsync(request, token); + await userService.DeleteAsync(request, token); return Ok(); } @@ -181,6 +224,12 @@ public async Task DeleteUserAsync(UserDeleteRequest request, Canc } } + /// + /// Email confirmation method + /// + /// + /// + /// [HttpGet("[action]")] [AllowAnonymous] public async Task ConfirmEmailAsync([FromQuery] ConfirmEmailRequest request, CancellationToken token) @@ -191,7 +240,7 @@ public async Task ConfirmEmailAsync([FromQuery] ConfirmEmailReque request.Email.NotNull(); request.Token.NotNull(); - await _userService.ConfirmCodeAsync(request.Email!, request.Token!, token); + await userService.ConfirmCodeAsync(request.Email!, request.Token!, token); return Ok(); } diff --git a/Botticelli.Server.Back/Extensions/StartupExtensions.cs b/Botticelli.Server.Back/Extensions/StartupExtensions.cs index a46f2dfa..54d0a780 100644 --- a/Botticelli.Server.Back/Extensions/StartupExtensions.cs +++ b/Botticelli.Server.Back/Extensions/StartupExtensions.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; +using Botticelli.Server.Back.Services.Broadcasting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https; @@ -22,6 +23,9 @@ public static void ApplyMigrations(this WebApplicationBuilder webAppli if (pendingMigrations.Any()) db.Database.Migrate(); } + + public static IServiceCollection AddBroadcasting(this IServiceCollection services) + => services.AddScoped(); public static IServiceCollection AddIdentity(this IServiceCollection services) { @@ -60,6 +64,8 @@ public static IServiceCollection AddIdentity(this IServiceCollection services) return services; } + + public static IWebHostBuilder AddSsl(this IWebHostBuilder builder, IConfiguration config) { // in Linux put here: ~/.dotnet/corefx/cryptography/x509stores/ @@ -86,12 +92,7 @@ public static IWebHostBuilder AddSsl(this IWebHostBuilder builder, IConfiguratio { ServerCertificate = certificate, ClientCertificateMode = ClientCertificateMode.AllowCertificate, - ClientCertificateValidation = (_, _, errors) => - { - if (errors != SslPolicyErrors.None) return false; - - return true; - } + ClientCertificateValidation = (_, _, errors) => errors == SslPolicyErrors.None }; listenOptions.Protocols = HttpProtocols.Http1AndHttp2; diff --git a/Botticelli.Server.Back/Program.cs b/Botticelli.Server.Back/Program.cs index 3b4e5557..76c2276c 100644 --- a/Botticelli.Server.Back/Program.cs +++ b/Botticelli.Server.Back/Program.cs @@ -60,7 +60,7 @@ Id = "Bearer" } }, - Array.Empty() + [] } }); }); @@ -93,6 +93,7 @@ .AddScoped() .AddSingleton() .AddScoped() + .AddBroadcasting() .AddDbContext(options => options.UseSqlite($"Data source={serverSettings.SecureStorageConnection}")) .AddDefaultIdentity>(options => options @@ -108,7 +109,6 @@ builder.Services.AddControllers(); - builder.ApplyMigrations(); var app = builder.Build(); diff --git a/Botticelli.Server.Back/Services/Auth/AdminAuthService.cs b/Botticelli.Server.Back/Services/Auth/AdminAuthService.cs index f64a7e79..daa03d93 100644 --- a/Botticelli.Server.Back/Services/Auth/AdminAuthService.cs +++ b/Botticelli.Server.Back/Services/Auth/AdminAuthService.cs @@ -1,7 +1,6 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; -using Botticelli.Server.Back.Settings; using Botticelli.Server.Data; using Botticelli.Server.Data.Entities.Auth; using Botticelli.Server.Data.Exceptions; @@ -9,7 +8,6 @@ using Botticelli.Shared.Utils; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; namespace Botticelli.Server.Back.Services.Auth; @@ -25,10 +23,9 @@ public class AdminAuthService : IAdminAuthService private readonly ILogger _logger; public AdminAuthService(IConfiguration config, - IHttpContextAccessor httpContextAccessor, - ServerDataContext context, - ILogger logger, - IOptionsMonitor settings) + IHttpContextAccessor httpContextAccessor, + ServerDataContext context, + ILogger logger) { _config = config; _httpContextAccessor = httpContextAccessor; @@ -41,22 +38,24 @@ public AdminAuthService(IConfiguration config, /// /// /// - public async Task HasUsersAsync() => - await _context - .ApplicationUsers - .AnyAsync(); + public async Task HasUsersAsync() + { + return await _context + .ApplicationUsers + .AnyAsync(); + } /// public async Task RegisterAsync(UserAddRequest userRegister) { try { - _logger.LogInformation($"{nameof(RegisterAsync)}({userRegister.UserName}) started..."); + _logger.LogInformation("{RegisterAsyncName}({UserRegisterUserName}) started...", nameof(RegisterAsync), userRegister.UserName); ValidateRequest(userRegister); if (_context.ApplicationUsers.AsQueryable() - .Any(u => u.NormalizedEmail == GetNormalized(userRegister.Email!))) + .Any(u => u.NormalizedEmail == GetNormalized(userRegister.Email!))) throw new DataException($"User with email {userRegister.Email} already exists!"); var user = new IdentityUser @@ -81,11 +80,11 @@ public async Task RegisterAsync(UserAddRequest userRegister) await _context.SaveChangesAsync(); - _logger.LogInformation($"{nameof(RegisterAsync)}({userRegister.UserName}) finished..."); + _logger.LogInformation("{RegisterAsyncName}({UserRegisterUserName}) finished...", nameof(RegisterAsync), userRegister.UserName); } catch (Exception ex) { - _logger.LogError($"{nameof(RegisterAsync)}({userRegister.UserName}) error: {ex.Message}", ex); + _logger.LogError("{RegisterAsyncName}({UserRegisterUserName}) error: {ExMessage}", nameof(RegisterAsync), userRegister.UserName, ex.Message, ex); } } @@ -95,21 +94,21 @@ public async Task RegeneratePassword(UserAddRequest userRegister) { try { - _logger.LogInformation($"{nameof(RegeneratePassword)}({userRegister.UserName}) started..."); + _logger.LogInformation("{RegeneratePasswordName}({UserRegisterUserName}) started...", nameof(RegeneratePassword), userRegister.UserName); ValidateRequest(userRegister); if (_context.ApplicationUsers.AsQueryable() - .Any(u => u.NormalizedEmail == GetNormalized(userRegister.Email!))) + .Any(u => u.NormalizedEmail == GetNormalized(userRegister.Email!))) throw new DataException($"User with email {userRegister.Email} already exists!"); await _context.SaveChangesAsync(); - _logger.LogInformation($"{nameof(RegeneratePassword)}({userRegister.UserName}) finished..."); + _logger.LogInformation("{RegeneratePasswordName}({UserRegisterUserName}) finished...", nameof(RegeneratePassword), userRegister.UserName); } catch (Exception ex) { - _logger.LogError($"{nameof(RegeneratePassword)}({userRegister.UserName}) error: {ex.Message}", ex); + _logger.LogError("{RegeneratePasswordName}({UserRegisterUserName}) error: {ExMessage}", nameof(RegeneratePassword), userRegister.UserName, ex.Message, ex); } } @@ -118,13 +117,13 @@ public async Task RegeneratePassword(UserAddRequest userRegister) { try { - _logger.LogInformation($"{nameof(GenerateToken)}({userLogin.Email}) started..."); + _logger.LogInformation("{GenerateTokenName}({UserLoginEmail}) started...", nameof(GenerateToken), userLogin.Email); ValidateRequest(userLogin); if (!CheckAccess(userLogin, false).result) { - _logger.LogInformation($"{nameof(GenerateToken)}({userLogin.Email}) access denied..."); + _logger.LogInformation("{GenerateTokenName}({UserLoginEmail}) access denied...", nameof(GenerateToken), userLogin.Email); return new GetTokenResponse { @@ -133,8 +132,8 @@ public async Task RegeneratePassword(UserAddRequest userRegister) } var user = _context.ApplicationUsers - .AsQueryable() - .FirstOrDefault(u => u.NormalizedEmail == GetNormalized(userLogin.Email)); + .AsQueryable() + .FirstOrDefault(u => u.NormalizedEmail == GetNormalized(userLogin.Email)); if (user == null) return new GetTokenResponse @@ -143,16 +142,16 @@ public async Task RegeneratePassword(UserAddRequest userRegister) }; var userRole = _context.ApplicationUserRoles - .AsQueryable() - .FirstOrDefault(ur => ur.UserId == user.Id); + .AsQueryable() + .FirstOrDefault(ur => ur.UserId == user.Id); var roleName = string.Empty; if (userRole != null) { var role = _context.ApplicationRoles - .AsQueryable() - .FirstOrDefault(r => r.Id == userRole.RoleId); + .AsQueryable() + .FirstOrDefault(r => r.Id == userRole.RoleId); roleName = role?.Name; } @@ -164,16 +163,15 @@ public async Task RegeneratePassword(UserAddRequest userRegister) new Claim("role", roleName ?? "no_role") }; - var key = new SymmetricSecurityKey( - Encoding.UTF8.GetBytes(_config["Authorization:Key"] ?? throw new InvalidOperationException())); + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Authorization:Key"] ?? throw new InvalidOperationException())); var signCreds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken(_config["Authorization:Issuer"], - _config["Authorization:Audience"], - claims, - expires: DateTime.Now.AddHours(24), // NOTE!!! Temporary! - //.AddMinutes(_settings.CurrentValue.TokenLifetimeMin), - signingCredentials: signCreds); + _config["Authorization:Audience"], + claims, + expires: DateTime.Now.AddHours(24), // NOTE!!! Temporary! + //.AddMinutes(_settings.CurrentValue.TokenLifetimeMin), + signingCredentials: signCreds); return new GetTokenResponse { @@ -183,7 +181,7 @@ public async Task RegeneratePassword(UserAddRequest userRegister) } catch (Exception ex) { - _logger.LogError(ex, $"{nameof(GenerateToken)}({userLogin.Email}) error {ex.Message}!"); + _logger.LogError(ex, "{GenerateTokenName}({UserLoginEmail}) error {ExMessage}!", nameof(GenerateToken), userLogin.Email, ex.Message); } return null; @@ -200,22 +198,22 @@ public bool CheckToken(string token) var sign = _config["Authorization:Key"] ?? throw new InvalidOperationException(); var handler = new JwtSecurityTokenHandler(); handler.ValidateToken(token, - new TokenValidationParameters - { - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(sign)), - ValidIssuer = _config["Authorization:Issuer"], - ValidateAudience = false - }, - out var validatedToken); + new TokenValidationParameters + { + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(sign)), + ValidIssuer = _config["Authorization:Issuer"], + ValidateAudience = false + }, + out var validatedToken); - _logger.LogInformation($"{nameof(CheckToken)}() validate token: {validatedToken != null}"); + _logger.LogInformation("{CheckTokenName}() validate token: {B}", nameof(CheckToken), validatedToken != null); return validatedToken != null; } catch (Exception ex) { - _logger.LogError($"{nameof(CheckToken)}() error: {ex.Message}"); + _logger.LogError("{CheckTokenName}() error: {ExMessage}", nameof(CheckToken), ex.Message); return false; } @@ -238,9 +236,9 @@ public bool CheckToken(string token) public string? GetCurrentUserId() { return _httpContextAccessor.HttpContext?.User - .Claims - .FirstOrDefault(c => c.Type == "applicationUserId") - ?.Value; + .Claims + .FirstOrDefault(c => c.Type == "applicationUserId") + ?.Value; } private static void ValidateRequest(UserAddRequest userRegister) @@ -258,5 +256,8 @@ private static void ValidateRequest(UserLoginRequest userLogin) userLogin.Password!.NotNullOrEmpty(); } - private static string GetNormalized(string input) => input.ToUpper(); + private static string GetNormalized(string input) + { + return input.ToUpper(); + } } \ No newline at end of file diff --git a/Botticelli.Server.Back/Services/Auth/PasswordSender.cs b/Botticelli.Server.Back/Services/Auth/PasswordSender.cs index 0411eac8..241d0536 100644 --- a/Botticelli.Server.Back/Services/Auth/PasswordSender.cs +++ b/Botticelli.Server.Back/Services/Auth/PasswordSender.cs @@ -20,9 +20,9 @@ public PasswordSender(ISender fluentEmail, ServerSettings serverSettings, ILogge public async Task SendPassword(string email, string password, CancellationToken ct) { - _logger.LogInformation($"Sending a password message to : {email}"); + _logger.LogInformation("Sending a password message to : {Email}", email); - if (EnvironmentExtensions.IsDevelopment()) _logger.LogInformation($"!!! Email : {email} password: {password} !!! ONLY FOR DEVELOPMENT PURPOSES !!!"); + if (EnvironmentExtensions.IsDevelopment()) _logger.LogInformation("!!! Email : {Email} password: {Password} !!! ONLY FOR DEVELOPMENT PURPOSES !!!", email, password); var message = Email.From(_serverSettings.ServerEmail, "BotticelliBots Admin Service") .To(email) @@ -35,11 +35,11 @@ public async Task SendPassword(string email, string password, CancellationToken if (!sendResult.Successful) { - _logger.LogError($"Sending a password message to : {email} error", sendResult.ErrorMessages); + _logger.LogError("Sending a password message to : {Email} error error mssage : {ErrMessage}", email, sendResult.ErrorMessages); throw new InvalidOperationException($"Sending mail errors: {string.Join(',', sendResult.ErrorMessages)}"); } - _logger.LogInformation($"Sending a password message to : {email} - OK"); + _logger.LogInformation("Sending a password message to : {Email} - OK", email); } } \ No newline at end of file diff --git a/Botticelli.Server.Back/Services/Auth/UserService.cs b/Botticelli.Server.Back/Services/Auth/UserService.cs index 637263dc..c2a197db 100644 --- a/Botticelli.Server.Back/Services/Auth/UserService.cs +++ b/Botticelli.Server.Back/Services/Auth/UserService.cs @@ -52,7 +52,7 @@ public async Task CheckAndAddAsync(UserAddRequest request, CancellationTok } catch (Exception ex) { - _logger.LogError($"{nameof(CheckAndAddAsync)}({request.UserName}) error: {ex.Message}", ex); + _logger.LogError("{CheckAndAddAsyncName}({RequestUserName}) error: {ExMessage} {Ex}", nameof(CheckAndAddAsync), request.UserName, ex.Message, ex); throw; } @@ -62,7 +62,7 @@ public async Task AddAsync(UserAddRequest request, bool needConfirmation, Cancel { try { - _logger.LogInformation($"{nameof(AddAsync)}({request.UserName}) started..."); + _logger.LogInformation("{AddAsyncName}({RequestUserName}) started...", nameof(AddAsync), request.UserName); request.NotNull(); request.UserName.NotNull(); request.Email.NotNull(); @@ -110,15 +110,15 @@ public async Task AddAsync(UserAddRequest request, bool needConfirmation, Cancel if (needConfirmation) { - _logger.LogInformation($"{nameof(AddAsync)}({request.UserName}) sending a confirmation email to {request.Email}..."); + _logger.LogInformation("{AddAsyncName}({RequestUserName}) sending a confirmation email to {RequestEmail}...", nameof(AddAsync), request.UserName, request.Email); await _confirmationService.SendConfirmationCode(user, token); } - _logger.LogInformation($"{nameof(AddAsync)}({request.UserName}) finished..."); + _logger.LogInformation("{AddAsyncName}({RequestUserName}) finished...", nameof(AddAsync), request.UserName); } catch (Exception ex) { - _logger.LogError($"{nameof(AddAsync)}({request.UserName}) error: {ex.Message}", ex); + _logger.LogError("{AddAsyncName}({RequestUserName}) error: {ExMessage}", nameof(AddAsync), request.UserName, ex.Message, ex); throw; } @@ -128,7 +128,7 @@ public async Task UpdateAsync(UserUpdateRequest request, CancellationToken token { try { - _logger.LogInformation($"{nameof(UpdateAsync)}({request.UserName}) started..."); + _logger.LogInformation("{UpdateAsyncName}({RequestUserName}) started...", nameof(UpdateAsync), request.UserName); request.NotNull(); request.UserName.NotNull(); @@ -154,11 +154,11 @@ public async Task UpdateAsync(UserUpdateRequest request, CancellationToken token await _context.SaveChangesAsync(token); - _logger.LogInformation($"{nameof(UpdateAsync)}({request.UserName}) finished..."); + _logger.LogInformation("{UpdateAsyncName}({RequestUserName}) finished...", nameof(UpdateAsync), request.UserName); } catch (Exception ex) { - _logger.LogError($"{nameof(UpdateAsync)}({request.UserName}) error: {ex.Message}", ex); + _logger.LogError("{UpdateAsyncName}({RequestUserName}) error: {ExMessage}", nameof(UpdateAsync), request.UserName, ex.Message, ex); throw; } @@ -168,7 +168,7 @@ public async Task DeleteAsync(UserDeleteRequest request, CancellationToken token { try { - _logger.LogInformation($"{nameof(DeleteAsync)}({request.UserName}) started..."); + _logger.LogInformation("{DeleteAsyncName}({RequestUserName}) started...", nameof(DeleteAsync), request.UserName); request.NotNull(); request.UserName.NotNull(); @@ -184,11 +184,11 @@ public async Task DeleteAsync(UserDeleteRequest request, CancellationToken token await _context.SaveChangesAsync(token); - _logger.LogInformation($"{nameof(DeleteAsync)}({request.UserName}) finished..."); + _logger.LogInformation("{DeleteAsyncName}({RequestUserName}) finished...", nameof(DeleteAsync), request.UserName); } catch (Exception ex) { - _logger.LogError($"{nameof(DeleteAsync)}({request.UserName}) error: {ex.Message}", ex); + _logger.LogError("{DeleteAsyncName}({RequestUserName}) error: {ExMessage}", nameof(DeleteAsync), request.UserName, ex.Message, ex); throw; } @@ -198,7 +198,7 @@ public async Task GetAsync(UserGetRequest request, Cancellation { try { - _logger.LogInformation($"{nameof(GetAsync)}({request.UserName}) started..."); + _logger.LogInformation("{GetAsyncName}({RequestUserName}) started...", nameof(GetAsync), request.UserName); if (_context.ApplicationUsers.AsQueryable() .All(u => u.NormalizedUserName != GetNormalized(request.UserName!))) @@ -209,7 +209,7 @@ public async Task GetAsync(UserGetRequest request, Cancellation user.NotNull(); user!.Email.NotNull(); - _logger.LogInformation($"{nameof(GetAsync)}({request.UserName}) finished..."); + _logger.LogInformation("{GetAsyncName}({RequestUserName}) finished...", nameof(GetAsync), request.UserName); return new UserGetResponse { @@ -219,7 +219,7 @@ public async Task GetAsync(UserGetRequest request, Cancellation } catch (Exception ex) { - _logger.LogError($"{nameof(GetAsync)}({request.UserName}) error: {ex.Message}", ex); + _logger.LogError("{GetAsyncName}({RequestUserName}) error: {ExMessage}", nameof(GetAsync), request.UserName, ex.Message, ex); throw; } diff --git a/Botticelli.Server.Back/Services/BotManagementService.cs b/Botticelli.Server.Back/Services/BotManagementService.cs index 6d784f5a..a756b56a 100644 --- a/Botticelli.Server.Back/Services/BotManagementService.cs +++ b/Botticelli.Server.Back/Services/BotManagementService.cs @@ -30,14 +30,14 @@ public BotManagementService(ServerDataContext context, /// /// public Task RegisterBot(string botId, - string? botKey, - string botName, - BotType botType, - Dictionary? additionalParams = null) + string? botKey, + string botName, + BotType botType, + Dictionary? additionalParams = null) { try { - _logger.LogInformation($"{nameof(RegisterBot)}({botId}, {botKey}, {botName}, {botType}) started..."); + _logger.LogInformation("{RegisterBotName}({BotId}, {BotKey}, {BotName}, {BotType}) started...", nameof(RegisterBot), botId, botKey, botName, botType); if (GetBotInfo(botId) == null) AddNewBotInfo(botId, @@ -144,7 +144,7 @@ public async Task UpdateBot(string botId, { try { - _logger.LogInformation($"{nameof(UpdateBot)}({botId}, {botKey}, {botName}) started..."); + _logger.LogInformation("{UpdateBotName}({BotId}, {BotKey}, {BotName}) started...", nameof(UpdateBot), botId, botKey, botName); var prevStatus = await GetRequiredBotStatus(botId); if (prevStatus is not BotStatus.Unlocked) await SetRequiredBotStatus(botId, BotStatus.Unlocked); @@ -153,7 +153,7 @@ public async Task UpdateBot(string botId, if (botInfo == null) { - _logger.LogInformation($"{nameof(UpdateBot)}() : bot with id '{botId}' wasn't found!"); + _logger.LogInformation("{UpdateBotName}() : bot with id '{BotId}' wasn't found!", nameof(UpdateBot), botId); return false; } diff --git a/Botticelli.Server.Back/Services/Broadcasting/BroadcastService.cs b/Botticelli.Server.Back/Services/Broadcasting/BroadcastService.cs index e3ddcf99..22972a11 100644 --- a/Botticelli.Server.Back/Services/Broadcasting/BroadcastService.cs +++ b/Botticelli.Server.Back/Services/Broadcasting/BroadcastService.cs @@ -15,15 +15,16 @@ public async Task BroadcastMessage(Broadcast message) await context.SaveChangesAsync(); } - public async Task> GetMessages(string botId) - { - return await context.BroadcastMessages.Where(m => m.BotId.Equals(botId)).ToArrayAsync(); - } + public async Task> GetMessages(string botId) => + await context.BroadcastMessages + .Where(m => m.BotId.Equals(botId) && !m.Received) + .Include(m => m.Attachments) + .ToArrayAsync(); public async Task MarkReceived(string botId, string messageId) { - var messages = context.BroadcastMessages.Where(bm => bm.BotId == botId && bm.Id == messageId) - .ToList(); + var messages = await context.BroadcastMessages.Where(bm => bm.BotId == botId && bm.Id == messageId) + .ToListAsync(); foreach (var message in messages) message.Received = true; @@ -32,24 +33,10 @@ public async Task MarkReceived(string botId, string messageId) await context.SaveChangesAsync(); } - public Task> GetBroadcasts(string botId) { var broadcasts = context.BroadcastMessages.Where(x => x.BotId == botId && !x.Sent && !x.Received).ToList(); return Task.FromResult(broadcasts); } - - public async Task MarkAsReceived(string messageId) - { - var broadcast = context.BroadcastMessages.FirstOrDefault(x => x.Id == messageId); - - if (broadcast == null) return; - - broadcast.Received = true; - - context.Update(broadcast); - - await context.SaveChangesAsync(); - } } \ No newline at end of file diff --git a/Botticelli.Server.Back/Services/Broadcasting/IBroadcastService.cs b/Botticelli.Server.Back/Services/Broadcasting/IBroadcastService.cs index 2333f724..ba89935c 100644 --- a/Botticelli.Server.Back/Services/Broadcasting/IBroadcastService.cs +++ b/Botticelli.Server.Back/Services/Broadcasting/IBroadcastService.cs @@ -29,7 +29,4 @@ public interface IBroadcastService /// /// public Task MarkReceived(string botId, string messageId); - - public Task> GetBroadcasts(string botId); - public Task MarkAsReceived(string messageId); } \ No newline at end of file diff --git a/Botticelli.Server.Back/Settings/ServerSettings.cs b/Botticelli.Server.Back/Settings/ServerSettings.cs index f4d83c28..d4aeefe7 100644 --- a/Botticelli.Server.Back/Settings/ServerSettings.cs +++ b/Botticelli.Server.Back/Settings/ServerSettings.cs @@ -18,4 +18,6 @@ public class ServerSettings public string? AnalyticsUrl { get; set; } public required string SecureStorageConnection { get; set; } public bool UseSsl { get; set; } + public int PasswordMinLength { get; set; } = 8; + public int PasswordMaxLength { get; set; } = 12; } \ No newline at end of file diff --git a/Botticelli.Server.Back/appsettings.json b/Botticelli.Server.Back/appsettings.json index 3ef7fdd0..644d7ac7 100644 --- a/Botticelli.Server.Back/appsettings.json +++ b/Botticelli.Server.Back/appsettings.json @@ -46,6 +46,8 @@ }, "serverEmail": "", "serverUrl": "", - "analyticsUrl": "" + "analyticsUrl": "", + "PasswordMinLength": 8, + "PasswordMaxLength": 12 } } \ No newline at end of file diff --git a/Botticelli.Server.Back/database.db b/Botticelli.Server.Back/database.db deleted file mode 100644 index ff7d1b6a..00000000 Binary files a/Botticelli.Server.Back/database.db and /dev/null differ diff --git a/Botticelli.Server.Data.Entities/Bot/Broadcasting/Broadcast.cs b/Botticelli.Server.Data.Entities/Bot/Broadcasting/Broadcast.cs index bb987443..81e05b76 100644 --- a/Botticelli.Server.Data.Entities/Bot/Broadcasting/Broadcast.cs +++ b/Botticelli.Server.Data.Entities/Bot/Broadcasting/Broadcast.cs @@ -11,9 +11,8 @@ public class Broadcast public required string BotId { get; set; } public required string Body { get; set; } + public List? Attachments { get; set; } public DateTime Timestamp { get; set; } - - public BroadcastAttachment[]? Attachments { get; set; } public bool Sent { get; set; } = false; public bool Received { get; set; } = false; } \ No newline at end of file diff --git a/Botticelli.Server.Data.Entities/Bot/Broadcasting/BroadcastAttachment.cs b/Botticelli.Server.Data.Entities/Bot/Broadcasting/BroadcastAttachment.cs index 61a141e5..2401a935 100644 --- a/Botticelli.Server.Data.Entities/Bot/Broadcasting/BroadcastAttachment.cs +++ b/Botticelli.Server.Data.Entities/Bot/Broadcasting/BroadcastAttachment.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Botticelli.Shared.Constants; namespace Botticelli.Server.Data.Entities.Bot.Broadcasting; @@ -9,6 +10,7 @@ public class BroadcastAttachment [Key] public Guid Id { get; set; } + public required string BroadcastId { get; set; } public MediaType MediaType { get; set; } public required string Filename { get; set; } public required byte[] Content { get; set; } diff --git a/Botticelli.Server.Data.Entities/Bot/Broadcasting/MediaTypes.cs b/Botticelli.Server.Data.Entities/Bot/Broadcasting/MediaTypes.cs deleted file mode 100644 index a726cb1c..00000000 --- a/Botticelli.Server.Data.Entities/Bot/Broadcasting/MediaTypes.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Botticelli.Server.Data.Entities.Bot.Broadcasting; - -public enum MediaType -{ - Audio, - Video, - Text, - Voice, - Image, - Sticker, - Contact, - Poll, - Document, - Unknown -} \ No newline at end of file diff --git a/Botticelli.Server.Data.Entities/Botticelli.Server.Data.Entities.csproj b/Botticelli.Server.Data.Entities/Botticelli.Server.Data.Entities.csproj index 506d05a1..2a5c5d15 100644 --- a/Botticelli.Server.Data.Entities/Botticelli.Server.Data.Entities.csproj +++ b/Botticelli.Server.Data.Entities/Botticelli.Server.Data.Entities.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli diff --git a/Botticelli.Server.Data/Botticelli.Server.Data.csproj b/Botticelli.Server.Data/Botticelli.Server.Data.csproj index 9b390c27..43b9631d 100644 --- a/Botticelli.Server.Data/Botticelli.Server.Data.csproj +++ b/Botticelli.Server.Data/Botticelli.Server.Data.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli @@ -14,14 +14,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Botticelli.Server.Data/Migrations/20230112102521_init.Designer.cs b/Botticelli.Server.Data/Migrations/20230112102521_init.Designer.cs deleted file mode 100644 index 26f0ba31..00000000 --- a/Botticelli.Server.Data/Migrations/20230112102521_init.Designer.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -using System; -using Botticelli.Server.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - [DbContext(typeof(ServerDataContext))] - [Migration("20230112102521_init")] - partial class init - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.2"); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.BotInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("BotName") - .HasColumnType("TEXT"); - - b.Property("LastKeepAlive") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.HasKey("BotId"); - - b.ToTable("BotInfo"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20230118100238_botType.Designer.cs b/Botticelli.Server.Data/Migrations/20230118100238_botType.Designer.cs deleted file mode 100644 index c6c52809..00000000 --- a/Botticelli.Server.Data/Migrations/20230118100238_botType.Designer.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -using System; -using Botticelli.Server.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - [DbContext(typeof(ServerDataContext))] - [Migration("20230118100238_botType")] - partial class botType - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.2"); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.BotInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("BotName") - .HasColumnType("TEXT"); - - b.Property("LastKeepAlive") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("BotId"); - - b.ToTable("BotInfo"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20230118100238_botType.cs b/Botticelli.Server.Data/Migrations/20230118100238_botType.cs deleted file mode 100644 index 3e4650a9..00000000 --- a/Botticelli.Server.Data/Migrations/20230118100238_botType.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - /// - public partial class botType : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Type", - table: "BotInfo", - type: "INTEGER", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Type", - table: "BotInfo"); - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20230311100413_auth.Designer.cs b/Botticelli.Server.Data/Migrations/20230311100413_auth.Designer.cs deleted file mode 100644 index 91fe8e01..00000000 --- a/Botticelli.Server.Data/Migrations/20230311100413_auth.Designer.cs +++ /dev/null @@ -1,131 +0,0 @@ -// -using System; -using Botticelli.Server.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - [DbContext(typeof(ServerDataContext))] - [Migration("20230311100413_auth")] - partial class auth - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.3"); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.BotInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("BotName") - .HasColumnType("TEXT"); - - b.Property("LastKeepAlive") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("BotId"); - - b.ToTable("BotInfo"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.ToTable("ApplicationUserRoles"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20230311100413_auth.cs b/Botticelli.Server.Data/Migrations/20230311100413_auth.cs deleted file mode 100644 index 67a558c0..00000000 --- a/Botticelli.Server.Data/Migrations/20230311100413_auth.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - /// - public partial class auth : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ApplicationRoles", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - NormalizedName = table.Column(type: "TEXT", nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ApplicationRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ApplicationUserRoles", - columns: table => new - { - UserId = table.Column(type: "TEXT", nullable: false), - RoleId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApplicationUserRoles", x => new { x.UserId, x.RoleId }); - }); - - migrationBuilder.CreateTable( - name: "ApplicationUsers", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - UserName = table.Column(type: "TEXT", nullable: true), - NormalizedUserName = table.Column(type: "TEXT", nullable: true), - Email = table.Column(type: "TEXT", nullable: true), - NormalizedEmail = table.Column(type: "TEXT", nullable: true), - EmailConfirmed = table.Column(type: "INTEGER", nullable: false), - PasswordHash = table.Column(type: "TEXT", nullable: true), - SecurityStamp = table.Column(type: "TEXT", nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), - PhoneNumber = table.Column(type: "TEXT", nullable: true), - PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), - TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), - LockoutEnd = table.Column(type: "TEXT", nullable: true), - LockoutEnabled = table.Column(type: "INTEGER", nullable: false), - AccessFailedCount = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApplicationUsers", x => x.Id); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ApplicationRoles"); - - migrationBuilder.DropTable( - name: "ApplicationUserRoles"); - - migrationBuilder.DropTable( - name: "ApplicationUsers"); - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20240913211216_botcontextInSqlite.Designer.cs b/Botticelli.Server.Data/Migrations/20240913211216_botcontextInSqlite.Designer.cs deleted file mode 100644 index 5874282f..00000000 --- a/Botticelli.Server.Data/Migrations/20240913211216_botcontextInSqlite.Designer.cs +++ /dev/null @@ -1,157 +0,0 @@ -// -using System; -using Botticelli.Server.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - [DbContext(typeof(ServerDataContext))] - [Migration("20240913211216_botcontextInSqlite")] - partial class botcontextInSqlite - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("BotKey") - .HasColumnType("TEXT"); - - b.Property("BotName") - .HasColumnType("TEXT"); - - b.Property("LastKeepAlive") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("BotId"); - - b.ToTable("BotInfo"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationRoles"); - - b.HasData( - new - { - Id = "91f76d24-9f30-446c-b3db-42a6cff5c1a6", - ConcurrencyStamp = "09/13/2024 21:12:15", - Name = "admin", - NormalizedName = "ADMIN" - }, - new - { - Id = "4b585771-7feb-4e6a-aa05-09076415edf1", - ConcurrencyStamp = "09/13/2024 21:12:15", - Name = "bot_manager", - NormalizedName = "BOT_MANAGER" - }, - new - { - Id = "4997b014-6ca0-4736-aa14-db77741c9fbb", - ConcurrencyStamp = "09/13/2024 21:12:15", - Name = "viewer", - NormalizedName = "VIEWER" - }); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.ToTable("ApplicationUserRoles"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20240913211216_botcontextInSqlite.cs b/Botticelli.Server.Data/Migrations/20240913211216_botcontextInSqlite.cs deleted file mode 100644 index fa04cb7a..00000000 --- a/Botticelli.Server.Data/Migrations/20240913211216_botcontextInSqlite.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Botticelli.Server.Data.Migrations -{ - /// - public partial class botcontextInSqlite : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.InsertData( - table: "ApplicationRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { "4997b014-6ca0-4736-aa14-db77741c9fbb", "09/13/2024 21:12:15", "viewer", "VIEWER" }, - { "4b585771-7feb-4e6a-aa05-09076415edf1", "09/13/2024 21:12:15", "bot_manager", "BOT_MANAGER" }, - { "91f76d24-9f30-446c-b3db-42a6cff5c1a6", "09/13/2024 21:12:15", "admin", "ADMIN" } - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "4997b014-6ca0-4736-aa14-db77741c9fbb"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "4b585771-7feb-4e6a-aa05-09076415edf1"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "91f76d24-9f30-446c-b3db-42a6cff5c1a6"); - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20240913211518_botAdditionalInfo.Designer.cs b/Botticelli.Server.Data/Migrations/20240913211518_botAdditionalInfo.Designer.cs deleted file mode 100644 index bac6f6d1..00000000 --- a/Botticelli.Server.Data/Migrations/20240913211518_botAdditionalInfo.Designer.cs +++ /dev/null @@ -1,192 +0,0 @@ -// -using System; -using Botticelli.Server.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - [DbContext(typeof(ServerDataContext))] - [Migration("20240913211518_botAdditionalInfo")] - partial class botAdditionalInfo - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotAdditionalInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("ItemName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ItemValue") - .HasColumnType("TEXT"); - - b.HasKey("BotId"); - - b.ToTable("BotAdditionalInfo"); - }); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("AdditionalInfoBotId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("BotKey") - .HasColumnType("TEXT"); - - b.Property("BotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LastKeepAlive") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("BotId"); - - b.HasIndex("AdditionalInfoBotId"); - - b.ToTable("BotInfo"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationRoles"); - - b.HasData( - new - { - Id = "2cb59f48-3d78-4056-b43a-5042b67d8b8d", - ConcurrencyStamp = "09/13/2024 21:15:18", - Name = "admin", - NormalizedName = "ADMIN" - }, - new - { - Id = "11594aea-0910-40f8-8e37-53efc5e1a80f", - ConcurrencyStamp = "09/13/2024 21:15:18", - Name = "bot_manager", - NormalizedName = "BOT_MANAGER" - }, - new - { - Id = "e8bcf27d-e87d-462c-bbbd-3fc1a42c5701", - ConcurrencyStamp = "09/13/2024 21:15:18", - Name = "viewer", - NormalizedName = "VIEWER" - }); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.ToTable("ApplicationUserRoles"); - }); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => - { - b.HasOne("Botticelli.Server.Data.Entities.Bot.BotAdditionalInfo", "AdditionalInfo") - .WithMany() - .HasForeignKey("AdditionalInfoBotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("AdditionalInfo"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20240913211518_botAdditionalInfo.cs b/Botticelli.Server.Data/Migrations/20240913211518_botAdditionalInfo.cs deleted file mode 100644 index 9602827b..00000000 --- a/Botticelli.Server.Data/Migrations/20240913211518_botAdditionalInfo.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Botticelli.Server.Data.Migrations -{ - /// - public partial class botAdditionalInfo : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "BotName", - table: "BotInfo", - type: "TEXT", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "TEXT", - oldNullable: true); - - migrationBuilder.AddColumn( - name: "AdditionalInfoBotId", - table: "BotInfo", - type: "TEXT", - nullable: false, - defaultValue: ""); - - migrationBuilder.CreateTable( - name: "BotAdditionalInfo", - columns: table => new - { - BotId = table.Column(type: "TEXT", nullable: false), - ItemName = table.Column(type: "TEXT", nullable: false), - ItemValue = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BotAdditionalInfo", x => x.BotId); - }); - - migrationBuilder.InsertData( - table: "ApplicationRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { "11594aea-0910-40f8-8e37-53efc5e1a80f", "09/13/2024 21:15:18", "bot_manager", "BOT_MANAGER" }, - { "2cb59f48-3d78-4056-b43a-5042b67d8b8d", "09/13/2024 21:15:18", "admin", "ADMIN" }, - { "e8bcf27d-e87d-462c-bbbd-3fc1a42c5701", "09/13/2024 21:15:18", "viewer", "VIEWER" } - }); - - migrationBuilder.CreateIndex( - name: "IX_BotInfo_AdditionalInfoBotId", - table: "BotInfo", - column: "AdditionalInfoBotId"); - - migrationBuilder.AddForeignKey( - name: "FK_BotInfo_BotAdditionalInfo_AdditionalInfoBotId", - table: "BotInfo", - column: "AdditionalInfoBotId", - principalTable: "BotAdditionalInfo", - principalColumn: "BotId", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_BotInfo_BotAdditionalInfo_AdditionalInfoBotId", - table: "BotInfo"); - - migrationBuilder.DropTable( - name: "BotAdditionalInfo"); - - migrationBuilder.DropIndex( - name: "IX_BotInfo_AdditionalInfoBotId", - table: "BotInfo"); - - migrationBuilder.DropColumn( - name: "AdditionalInfoBotId", - table: "BotInfo"); - - migrationBuilder.AlterColumn( - name: "BotName", - table: "BotInfo", - type: "TEXT", - nullable: true, - oldClrType: typeof(string), - oldType: "TEXT"); - - migrationBuilder.InsertData( - table: "ApplicationRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { "4997b014-6ca0-4736-aa14-db77741c9fbb", "09/13/2024 21:12:15", "viewer", "VIEWER" }, - { "4b585771-7feb-4e6a-aa05-09076415edf1", "09/13/2024 21:12:15", "bot_manager", "BOT_MANAGER" }, - { "91f76d24-9f30-446c-b3db-42a6cff5c1a6", "09/13/2024 21:12:15", "admin", "ADMIN" } - }); - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20241014112906_OneDataSource.Designer.cs b/Botticelli.Server.Data/Migrations/20241014112906_OneDataSource.Designer.cs deleted file mode 100644 index 77882574..00000000 --- a/Botticelli.Server.Data/Migrations/20241014112906_OneDataSource.Designer.cs +++ /dev/null @@ -1,192 +0,0 @@ -// -using System; -using Botticelli.Server.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Botticelli.Server.Data.Migrations -{ - [DbContext(typeof(ServerDataContext))] - [Migration("20241014112906_OneDataSource")] - partial class OneDataSource - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotAdditionalInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("BotInfoBotId") - .HasColumnType("TEXT"); - - b.Property("ItemName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ItemValue") - .HasColumnType("TEXT"); - - b.HasKey("BotId"); - - b.HasIndex("BotInfoBotId"); - - b.ToTable("BotAdditionalInfo"); - }); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => - { - b.Property("BotId") - .HasColumnType("TEXT"); - - b.Property("BotKey") - .HasColumnType("TEXT"); - - b.Property("BotName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LastKeepAlive") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("BotId"); - - b.ToTable("BotInfo"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NormalizedName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationRoles"); - - b.HasData( - new - { - Id = "969600de-70d9-43e9-bc01-56415c57863b", - ConcurrencyStamp = "10/14/2024 11:29:05", - Name = "admin", - NormalizedName = "ADMIN" - }, - new - { - Id = "b57f3fda-a6d3-4f93-8faa-ce99a06a476d", - ConcurrencyStamp = "10/14/2024 11:29:05", - Name = "bot_manager", - NormalizedName = "BOT_MANAGER" - }, - new - { - Id = "22c98256-7794-4d42-bdc8-4725445de3c9", - ConcurrencyStamp = "10/14/2024 11:29:05", - Name = "viewer", - NormalizedName = "VIEWER" - }); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ApplicationUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.ToTable("ApplicationUserRoles"); - }); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotAdditionalInfo", b => - { - b.HasOne("Botticelli.Server.Data.Entities.Bot.BotInfo", null) - .WithMany("AdditionalInfo") - .HasForeignKey("BotInfoBotId"); - }); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => - { - b.Navigation("AdditionalInfo"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20241014112906_OneDataSource.cs b/Botticelli.Server.Data/Migrations/20241014112906_OneDataSource.cs deleted file mode 100644 index e7e5277d..00000000 --- a/Botticelli.Server.Data/Migrations/20241014112906_OneDataSource.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Botticelli.Server.Data.Migrations -{ - /// - public partial class OneDataSource : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_BotInfo_BotAdditionalInfo_AdditionalInfoBotId", - table: "BotInfo"); - - migrationBuilder.DropIndex( - name: "IX_BotInfo_AdditionalInfoBotId", - table: "BotInfo"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "11594aea-0910-40f8-8e37-53efc5e1a80f"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "2cb59f48-3d78-4056-b43a-5042b67d8b8d"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "e8bcf27d-e87d-462c-bbbd-3fc1a42c5701"); - - migrationBuilder.DropColumn( - name: "AdditionalInfoBotId", - table: "BotInfo"); - - migrationBuilder.AddColumn( - name: "BotInfoBotId", - table: "BotAdditionalInfo", - type: "TEXT", - nullable: true); - - migrationBuilder.InsertData( - table: "ApplicationRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { "22c98256-7794-4d42-bdc8-4725445de3c9", "10/14/2024 11:29:05", "viewer", "VIEWER" }, - { "969600de-70d9-43e9-bc01-56415c57863b", "10/14/2024 11:29:05", "admin", "ADMIN" }, - { "b57f3fda-a6d3-4f93-8faa-ce99a06a476d", "10/14/2024 11:29:05", "bot_manager", "BOT_MANAGER" } - }); - - migrationBuilder.CreateIndex( - name: "IX_BotAdditionalInfo_BotInfoBotId", - table: "BotAdditionalInfo", - column: "BotInfoBotId"); - - migrationBuilder.AddForeignKey( - name: "FK_BotAdditionalInfo_BotInfo_BotInfoBotId", - table: "BotAdditionalInfo", - column: "BotInfoBotId", - principalTable: "BotInfo", - principalColumn: "BotId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_BotAdditionalInfo_BotInfo_BotInfoBotId", - table: "BotAdditionalInfo"); - - migrationBuilder.DropIndex( - name: "IX_BotAdditionalInfo_BotInfoBotId", - table: "BotAdditionalInfo"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "22c98256-7794-4d42-bdc8-4725445de3c9"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "969600de-70d9-43e9-bc01-56415c57863b"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "b57f3fda-a6d3-4f93-8faa-ce99a06a476d"); - - migrationBuilder.DropColumn( - name: "BotInfoBotId", - table: "BotAdditionalInfo"); - - migrationBuilder.AddColumn( - name: "AdditionalInfoBotId", - table: "BotInfo", - type: "TEXT", - nullable: false, - defaultValue: ""); - - migrationBuilder.InsertData( - table: "ApplicationRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { "11594aea-0910-40f8-8e37-53efc5e1a80f", "09/13/2024 21:15:18", "bot_manager", "BOT_MANAGER" }, - { "2cb59f48-3d78-4056-b43a-5042b67d8b8d", "09/13/2024 21:15:18", "admin", "ADMIN" }, - { "e8bcf27d-e87d-462c-bbbd-3fc1a42c5701", "09/13/2024 21:15:18", "viewer", "VIEWER" } - }); - - migrationBuilder.CreateIndex( - name: "IX_BotInfo_AdditionalInfoBotId", - table: "BotInfo", - column: "AdditionalInfoBotId"); - - migrationBuilder.AddForeignKey( - name: "FK_BotInfo_BotAdditionalInfo_AdditionalInfoBotId", - table: "BotInfo", - column: "AdditionalInfoBotId", - principalTable: "BotAdditionalInfo", - principalColumn: "BotId", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20241201155452_BroadCastingServer.cs b/Botticelli.Server.Data/Migrations/20241201155452_BroadCastingServer.cs deleted file mode 100644 index 1b0c801e..00000000 --- a/Botticelli.Server.Data/Migrations/20241201155452_BroadCastingServer.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Botticelli.Server.Data.Migrations -{ - /// - public partial class BroadCastingServer : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Broadcasts", - columns: table => new - { - BotId = table.Column(type: "TEXT", nullable: false), - Body = table.Column(type: "TEXT", nullable: false), - Timestamp = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Broadcasts", x => x.BotId); - }); - - migrationBuilder.CreateTable( - name: "BroadcastAttachments", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - MediaType = table.Column(type: "INTEGER", nullable: false), - Filename = table.Column(type: "TEXT", nullable: false), - Content = table.Column(type: "BLOB", nullable: false), - BroadcastBotId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BroadcastAttachments", x => x.Id); - table.ForeignKey( - name: "FK_BroadcastAttachments_Broadcasts_BroadcastBotId", - column: x => x.BroadcastBotId, - principalTable: "Broadcasts", - principalColumn: "BotId"); - }); - - migrationBuilder.CreateIndex( - name: "IX_BroadcastAttachments_BroadcastBotId", - table: "BroadcastAttachments", - column: "BroadcastBotId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "BroadcastAttachments"); - - migrationBuilder.DropTable( - name: "Broadcasts"); - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20250310193726_broadcasting.cs b/Botticelli.Server.Data/Migrations/20250310193726_broadcasting.cs deleted file mode 100644 index fcaaef0d..00000000 --- a/Botticelli.Server.Data/Migrations/20250310193726_broadcasting.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Botticelli.Server.Data.Migrations -{ - /// - public partial class broadcasting : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_BroadcastAttachments_Broadcasts_BroadcastBotId", - table: "BroadcastAttachments"); - - migrationBuilder.DropPrimaryKey( - name: "PK_Broadcasts", - table: "Broadcasts"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "0704f04d-1faa-44ab-b0fa-3ec05a168255"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "1027bf58-8ea4-4042-9651-690ef7eff777"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "19291d61-00bc-497c-8162-aa1ed022d1a7"); - - migrationBuilder.RenameColumn( - name: "BroadcastBotId", - table: "BroadcastAttachments", - newName: "BroadcastId"); - - migrationBuilder.RenameIndex( - name: "IX_BroadcastAttachments_BroadcastBotId", - table: "BroadcastAttachments", - newName: "IX_BroadcastAttachments_BroadcastId"); - - migrationBuilder.AddColumn( - name: "Id", - table: "Broadcasts", - type: "TEXT", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "Received", - table: "Broadcasts", - type: "INTEGER", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "Sent", - table: "Broadcasts", - type: "INTEGER", - nullable: false, - defaultValue: false); - - migrationBuilder.AddPrimaryKey( - name: "PK_Broadcasts", - table: "Broadcasts", - column: "Id"); - - migrationBuilder.InsertData( - table: "ApplicationRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { "2e960a01-b27e-4435-961c-231e9276a6a4", "03/10/2025 19:37:25", "viewer", "VIEWER" }, - { "85ca9cb0-e520-461b-8b2e-98877f519efd", "03/10/2025 19:37:25", "bot_manager", "BOT_MANAGER" }, - { "ebdb9ace-7a62-41cf-b4e3-749be50310e2", "03/10/2025 19:37:25", "admin", "ADMIN" } - }); - - migrationBuilder.AddForeignKey( - name: "FK_BroadcastAttachments_Broadcasts_BroadcastId", - table: "BroadcastAttachments", - column: "BroadcastId", - principalTable: "Broadcasts", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_BroadcastAttachments_Broadcasts_BroadcastId", - table: "BroadcastAttachments"); - - migrationBuilder.DropPrimaryKey( - name: "PK_Broadcasts", - table: "Broadcasts"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "2e960a01-b27e-4435-961c-231e9276a6a4"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "85ca9cb0-e520-461b-8b2e-98877f519efd"); - - migrationBuilder.DeleteData( - table: "ApplicationRoles", - keyColumn: "Id", - keyValue: "ebdb9ace-7a62-41cf-b4e3-749be50310e2"); - - migrationBuilder.DropColumn( - name: "Id", - table: "Broadcasts"); - - migrationBuilder.DropColumn( - name: "Received", - table: "Broadcasts"); - - migrationBuilder.DropColumn( - name: "Sent", - table: "Broadcasts"); - - migrationBuilder.RenameColumn( - name: "BroadcastId", - table: "BroadcastAttachments", - newName: "BroadcastBotId"); - - migrationBuilder.RenameIndex( - name: "IX_BroadcastAttachments_BroadcastId", - table: "BroadcastAttachments", - newName: "IX_BroadcastAttachments_BroadcastBotId"); - - migrationBuilder.AddPrimaryKey( - name: "PK_Broadcasts", - table: "Broadcasts", - column: "BotId"); - - migrationBuilder.InsertData( - table: "ApplicationRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { "0704f04d-1faa-44ab-b0fa-3ec05a168255", "12/01/2024 15:54:52", "viewer", "VIEWER" }, - { "1027bf58-8ea4-4042-9651-690ef7eff777", "12/01/2024 15:54:52", "admin", "ADMIN" }, - { "19291d61-00bc-497c-8162-aa1ed022d1a7", "12/01/2024 15:54:52", "bot_manager", "BOT_MANAGER" } - }); - - migrationBuilder.AddForeignKey( - name: "FK_BroadcastAttachments_Broadcasts_BroadcastBotId", - table: "BroadcastAttachments", - column: "BroadcastBotId", - principalTable: "Broadcasts", - principalColumn: "BotId"); - } - } -} diff --git a/Botticelli.Server.Data/Migrations/20241201155452_BroadCastingServer.Designer.cs b/Botticelli.Server.Data/Migrations/20250612110926_initial.Designer.cs similarity index 85% rename from Botticelli.Server.Data/Migrations/20241201155452_BroadCastingServer.Designer.cs rename to Botticelli.Server.Data/Migrations/20250612110926_initial.Designer.cs index d6080461..b4b3daf8 100644 --- a/Botticelli.Server.Data/Migrations/20241201155452_BroadCastingServer.Designer.cs +++ b/Botticelli.Server.Data/Migrations/20250612110926_initial.Designer.cs @@ -11,14 +11,14 @@ namespace Botticelli.Server.Data.Migrations { [DbContext(typeof(ServerDataContext))] - [Migration("20241201155452_BroadCastingServer")] - partial class BroadCastingServer + [Migration("20250612110926_initial")] + partial class initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotAdditionalInfo", b => { @@ -70,17 +70,27 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.Broadcasting.Broadcast", b => { - b.Property("BotId") + b.Property("Id") .HasColumnType("TEXT"); b.Property("Body") .IsRequired() .HasColumnType("TEXT"); + b.Property("BotId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Received") + .HasColumnType("INTEGER"); + + b.Property("Sent") + .HasColumnType("INTEGER"); + b.Property("Timestamp") .HasColumnType("TEXT"); - b.HasKey("BotId"); + b.HasKey("Id"); b.ToTable("Broadcasts"); }); @@ -91,9 +101,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("BroadcastBotId") - .HasColumnType("TEXT"); - b.Property("Content") .IsRequired() .HasColumnType("BLOB"); @@ -107,8 +114,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("BroadcastBotId"); - b.ToTable("BroadcastAttachments"); }); @@ -133,22 +138,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasData( new { - Id = "1027bf58-8ea4-4042-9651-690ef7eff777", - ConcurrencyStamp = "12/01/2024 15:54:52", + Id = "155a1281-61f2-4cea-b7b4-6f97b30d48d7", + ConcurrencyStamp = "06/12/2025 11:09:25", Name = "admin", NormalizedName = "ADMIN" }, new { - Id = "19291d61-00bc-497c-8162-aa1ed022d1a7", - ConcurrencyStamp = "12/01/2024 15:54:52", + Id = "90283ad1-e01d-436b-879d-75bdab45fdfd", + ConcurrencyStamp = "06/12/2025 11:09:25", Name = "bot_manager", NormalizedName = "BOT_MANAGER" }, new { - Id = "0704f04d-1faa-44ab-b0fa-3ec05a168255", - ConcurrencyStamp = "12/01/2024 15:54:52", + Id = "e56de249-c6df-4699-9874-96d8247e7e57", + ConcurrencyStamp = "06/12/2025 11:09:25", Name = "viewer", NormalizedName = "VIEWER" }); @@ -226,22 +231,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("BotInfoBotId"); }); - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.Broadcasting.BroadcastAttachment", b => - { - b.HasOne("Botticelli.Server.Data.Entities.Bot.Broadcasting.Broadcast", null) - .WithMany("Attachments") - .HasForeignKey("BroadcastBotId"); - }); - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => { b.Navigation("AdditionalInfo"); }); - - modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.Broadcasting.Broadcast", b => - { - b.Navigation("Attachments"); - }); #pragma warning restore 612, 618 } } diff --git a/Botticelli.Server.Data/Migrations/20250612110926_initial.cs b/Botticelli.Server.Data/Migrations/20250612110926_initial.cs new file mode 100644 index 00000000..949f9d9d --- /dev/null +++ b/Botticelli.Server.Data/Migrations/20250612110926_initial.cs @@ -0,0 +1,173 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Botticelli.Server.Data.Migrations +{ + /// + public partial class initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApplicationRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + NormalizedName = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApplicationUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationUserRoles", x => new { x.UserId, x.RoleId }); + }); + + migrationBuilder.CreateTable( + name: "ApplicationUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + UserName = table.Column(type: "TEXT", nullable: true), + NormalizedUserName = table.Column(type: "TEXT", nullable: true), + Email = table.Column(type: "TEXT", nullable: true), + NormalizedEmail = table.Column(type: "TEXT", nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BotInfo", + columns: table => new + { + BotId = table.Column(type: "TEXT", nullable: false), + BotName = table.Column(type: "TEXT", nullable: false), + LastKeepAlive = table.Column(type: "TEXT", nullable: true), + Status = table.Column(type: "INTEGER", nullable: true), + Type = table.Column(type: "INTEGER", nullable: true), + BotKey = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BotInfo", x => x.BotId); + }); + + migrationBuilder.CreateTable( + name: "BroadcastAttachments", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + MediaType = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false), + Content = table.Column(type: "BLOB", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BroadcastAttachments", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Broadcasts", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + BotId = table.Column(type: "TEXT", nullable: false), + Body = table.Column(type: "TEXT", nullable: false), + Timestamp = table.Column(type: "TEXT", nullable: false), + Sent = table.Column(type: "INTEGER", nullable: false), + Received = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Broadcasts", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BotAdditionalInfo", + columns: table => new + { + BotId = table.Column(type: "TEXT", nullable: false), + ItemName = table.Column(type: "TEXT", nullable: false), + ItemValue = table.Column(type: "TEXT", nullable: true), + BotInfoBotId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BotAdditionalInfo", x => x.BotId); + table.ForeignKey( + name: "FK_BotAdditionalInfo_BotInfo_BotInfoBotId", + column: x => x.BotInfoBotId, + principalTable: "BotInfo", + principalColumn: "BotId"); + }); + + migrationBuilder.InsertData( + table: "ApplicationRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { "155a1281-61f2-4cea-b7b4-6f97b30d48d7", "06/12/2025 11:09:25", "admin", "ADMIN" }, + { "90283ad1-e01d-436b-879d-75bdab45fdfd", "06/12/2025 11:09:25", "bot_manager", "BOT_MANAGER" }, + { "e56de249-c6df-4699-9874-96d8247e7e57", "06/12/2025 11:09:25", "viewer", "VIEWER" } + }); + + migrationBuilder.CreateIndex( + name: "IX_BotAdditionalInfo_BotInfoBotId", + table: "BotAdditionalInfo", + column: "BotInfoBotId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApplicationRoles"); + + migrationBuilder.DropTable( + name: "ApplicationUserRoles"); + + migrationBuilder.DropTable( + name: "ApplicationUsers"); + + migrationBuilder.DropTable( + name: "BotAdditionalInfo"); + + migrationBuilder.DropTable( + name: "BroadcastAttachments"); + + migrationBuilder.DropTable( + name: "Broadcasts"); + + migrationBuilder.DropTable( + name: "BotInfo"); + } + } +} diff --git a/Botticelli.Server.Data/Migrations/20250310193726_broadcasting.Designer.cs b/Botticelli.Server.Data/Migrations/20250921100822_BroadcastIdField.Designer.cs similarity index 91% rename from Botticelli.Server.Data/Migrations/20250310193726_broadcasting.Designer.cs rename to Botticelli.Server.Data/Migrations/20250921100822_BroadcastIdField.Designer.cs index 76e068f8..f18e4a88 100644 --- a/Botticelli.Server.Data/Migrations/20250310193726_broadcasting.Designer.cs +++ b/Botticelli.Server.Data/Migrations/20250921100822_BroadcastIdField.Designer.cs @@ -11,14 +11,14 @@ namespace Botticelli.Server.Data.Migrations { [DbContext(typeof(ServerDataContext))] - [Migration("20250310193726_broadcasting")] - partial class broadcasting + [Migration("20250921100822_BroadcastIdField")] + partial class BroadcastIdField { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotAdditionalInfo", b => { @@ -102,6 +102,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("BroadcastId") + .IsRequired() .HasColumnType("TEXT"); b.Property("Content") @@ -143,22 +144,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasData( new { - Id = "ebdb9ace-7a62-41cf-b4e3-749be50310e2", - ConcurrencyStamp = "03/10/2025 19:37:25", + Id = "abb60b3b-0b22-487f-a88f-9cac88d9e925", + ConcurrencyStamp = "09/21/2025 10:08:21", Name = "admin", NormalizedName = "ADMIN" }, new { - Id = "85ca9cb0-e520-461b-8b2e-98877f519efd", - ConcurrencyStamp = "03/10/2025 19:37:25", + Id = "9422072d-2306-434d-86a8-300d1a55f2ff", + ConcurrencyStamp = "09/21/2025 10:08:21", Name = "bot_manager", NormalizedName = "BOT_MANAGER" }, new { - Id = "2e960a01-b27e-4435-961c-231e9276a6a4", - ConcurrencyStamp = "03/10/2025 19:37:25", + Id = "693cd866-21b0-4003-84a6-cdf57c85a6a7", + ConcurrencyStamp = "09/21/2025 10:08:21", Name = "viewer", NormalizedName = "VIEWER" }); @@ -240,7 +241,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("Botticelli.Server.Data.Entities.Bot.Broadcasting.Broadcast", null) .WithMany("Attachments") - .HasForeignKey("BroadcastId"); + .HasForeignKey("BroadcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => diff --git a/Botticelli.Server.Data/Migrations/20250921100822_BroadcastIdField.cs b/Botticelli.Server.Data/Migrations/20250921100822_BroadcastIdField.cs new file mode 100644 index 00000000..9a6aab82 --- /dev/null +++ b/Botticelli.Server.Data/Migrations/20250921100822_BroadcastIdField.cs @@ -0,0 +1,102 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Botticelli.Server.Data.Migrations +{ + /// + public partial class BroadcastIdField : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "ApplicationRoles", + keyColumn: "Id", + keyValue: "155a1281-61f2-4cea-b7b4-6f97b30d48d7"); + + migrationBuilder.DeleteData( + table: "ApplicationRoles", + keyColumn: "Id", + keyValue: "90283ad1-e01d-436b-879d-75bdab45fdfd"); + + migrationBuilder.DeleteData( + table: "ApplicationRoles", + keyColumn: "Id", + keyValue: "e56de249-c6df-4699-9874-96d8247e7e57"); + + migrationBuilder.AddColumn( + name: "BroadcastId", + table: "BroadcastAttachments", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.InsertData( + table: "ApplicationRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { "693cd866-21b0-4003-84a6-cdf57c85a6a7", "09/21/2025 10:08:21", "viewer", "VIEWER" }, + { "9422072d-2306-434d-86a8-300d1a55f2ff", "09/21/2025 10:08:21", "bot_manager", "BOT_MANAGER" }, + { "abb60b3b-0b22-487f-a88f-9cac88d9e925", "09/21/2025 10:08:21", "admin", "ADMIN" } + }); + + migrationBuilder.CreateIndex( + name: "IX_BroadcastAttachments_BroadcastId", + table: "BroadcastAttachments", + column: "BroadcastId"); + + migrationBuilder.AddForeignKey( + name: "FK_BroadcastAttachments_Broadcasts_BroadcastId", + table: "BroadcastAttachments", + column: "BroadcastId", + principalTable: "Broadcasts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_BroadcastAttachments_Broadcasts_BroadcastId", + table: "BroadcastAttachments"); + + migrationBuilder.DropIndex( + name: "IX_BroadcastAttachments_BroadcastId", + table: "BroadcastAttachments"); + + migrationBuilder.DeleteData( + table: "ApplicationRoles", + keyColumn: "Id", + keyValue: "693cd866-21b0-4003-84a6-cdf57c85a6a7"); + + migrationBuilder.DeleteData( + table: "ApplicationRoles", + keyColumn: "Id", + keyValue: "9422072d-2306-434d-86a8-300d1a55f2ff"); + + migrationBuilder.DeleteData( + table: "ApplicationRoles", + keyColumn: "Id", + keyValue: "abb60b3b-0b22-487f-a88f-9cac88d9e925"); + + migrationBuilder.DropColumn( + name: "BroadcastId", + table: "BroadcastAttachments"); + + migrationBuilder.InsertData( + table: "ApplicationRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { "155a1281-61f2-4cea-b7b4-6f97b30d48d7", "06/12/2025 11:09:25", "admin", "ADMIN" }, + { "90283ad1-e01d-436b-879d-75bdab45fdfd", "06/12/2025 11:09:25", "bot_manager", "BOT_MANAGER" }, + { "e56de249-c6df-4699-9874-96d8247e7e57", "06/12/2025 11:09:25", "viewer", "VIEWER" } + }); + } + } +} diff --git a/Botticelli.Server.Data/Migrations/BotInfoContextModelSnapshot.cs b/Botticelli.Server.Data/Migrations/ServerDataContextModelSnapshot.cs similarity index 91% rename from Botticelli.Server.Data/Migrations/BotInfoContextModelSnapshot.cs rename to Botticelli.Server.Data/Migrations/ServerDataContextModelSnapshot.cs index 2f053004..e7231a24 100644 --- a/Botticelli.Server.Data/Migrations/BotInfoContextModelSnapshot.cs +++ b/Botticelli.Server.Data/Migrations/ServerDataContextModelSnapshot.cs @@ -10,12 +10,12 @@ namespace Botticelli.Server.Data.Migrations { [DbContext(typeof(ServerDataContext))] - partial class BotInfoContextModelSnapshot : ModelSnapshot + partial class ServerDataContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.16"); modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotAdditionalInfo", b => { @@ -99,6 +99,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("BroadcastId") + .IsRequired() .HasColumnType("TEXT"); b.Property("Content") @@ -140,22 +141,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasData( new { - Id = "ebdb9ace-7a62-41cf-b4e3-749be50310e2", - ConcurrencyStamp = "03/10/2025 19:37:25", + Id = "abb60b3b-0b22-487f-a88f-9cac88d9e925", + ConcurrencyStamp = "09/21/2025 10:08:21", Name = "admin", NormalizedName = "ADMIN" }, new { - Id = "85ca9cb0-e520-461b-8b2e-98877f519efd", - ConcurrencyStamp = "03/10/2025 19:37:25", + Id = "9422072d-2306-434d-86a8-300d1a55f2ff", + ConcurrencyStamp = "09/21/2025 10:08:21", Name = "bot_manager", NormalizedName = "BOT_MANAGER" }, new { - Id = "2e960a01-b27e-4435-961c-231e9276a6a4", - ConcurrencyStamp = "03/10/2025 19:37:25", + Id = "693cd866-21b0-4003-84a6-cdf57c85a6a7", + ConcurrencyStamp = "09/21/2025 10:08:21", Name = "viewer", NormalizedName = "VIEWER" }); @@ -237,7 +238,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Botticelli.Server.Data.Entities.Bot.Broadcasting.Broadcast", null) .WithMany("Attachments") - .HasForeignKey("BroadcastId"); + .HasForeignKey("BroadcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Botticelli.Server.Data.Entities.Bot.BotInfo", b => diff --git a/Botticelli.Server.FrontNew/Botticelli.Server.FrontNew.csproj b/Botticelli.Server.FrontNew/Botticelli.Server.FrontNew.csproj index ebab3772..bf3d9d20 100644 --- a/Botticelli.Server.FrontNew/Botticelli.Server.FrontNew.csproj +++ b/Botticelli.Server.FrontNew/Botticelli.Server.FrontNew.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli @@ -20,8 +20,8 @@ - - + + @@ -62,15 +62,15 @@ PreserveNewest - - PreserveNewest - PreserveNewest PreserveNewest + + PreserveNewest + diff --git a/Botticelli.Server.FrontNew/Handler/AuthDelegatingHandler.cs b/Botticelli.Server.FrontNew/Handler/AuthDelegatingHandler.cs index a8ac5fd7..fe5d8da5 100644 --- a/Botticelli.Server.FrontNew/Handler/AuthDelegatingHandler.cs +++ b/Botticelli.Server.FrontNew/Handler/AuthDelegatingHandler.cs @@ -1,31 +1,19 @@ using System.Net.Http.Headers; -using System.Security.Authentication; using Botticelli.Server.FrontNew.Clients; namespace Botticelli.Server.FrontNew.Handler; -public class AuthDelegatingHandler : DelegatingHandler +public class AuthDelegatingHandler(SessionClient sessionClient) : DelegatingHandler { - private readonly SessionClient _sessionClient; - - public AuthDelegatingHandler(SessionClient sessionClient) - { - _sessionClient = sessionClient; - } - protected override async Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) + CancellationToken cancellationToken) { - var session = _sessionClient.GetSession(); - - Console.WriteLine($"Botticelli.Auth.Sample.Telegram delegating got session: {session?.Token}"); + var session = sessionClient.GetSession(); - if (session == null) throw new AuthenticationException("Can't find session!"); + if (session == null) throw new UnauthorizedAccessException("Can't find session!"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", session.Token); - var response = await base.SendAsync(request, cancellationToken); - - return response; + return await base.SendAsync(request, cancellationToken); } } \ No newline at end of file diff --git a/Botticelli.Server.FrontNew/Models/BotBroadcastModel.cs b/Botticelli.Server.FrontNew/Models/BotBroadcastModel.cs new file mode 100644 index 00000000..95ef060e --- /dev/null +++ b/Botticelli.Server.FrontNew/Models/BotBroadcastModel.cs @@ -0,0 +1,8 @@ +namespace Botticelli.Server.FrontNew.Models; + +internal class BotBroadcastModel +{ + public string BotId { get; set; } + public string BotName { get; set; } + public string Message { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Server.FrontNew/Pages/BotBroadcast.razor b/Botticelli.Server.FrontNew/Pages/BotBroadcast.razor new file mode 100644 index 00000000..5883f38b --- /dev/null +++ b/Botticelli.Server.FrontNew/Pages/BotBroadcast.razor @@ -0,0 +1,199 @@ +@page "/broadcast/send/{botId}" +@using System.Net.Http.Headers +@using System.Text +@using System.Text.Json +@using Botticelli.Server.FrontNew.Models +@using Botticelli.Server.FrontNew.Settings +@using Botticelli.Server.FrontNew.Utils +@using Botticelli.Shared.Constants +@using Botticelli.Shared.Utils +@using Botticelli.Shared.ValueObjects +@using Flurl +@using Microsoft.Extensions.Options +@inject HttpClient Http +@inject NavigationManager UriHelper; +@inject IOptionsMonitor BackSettings; +@inject CookieStorageAccessor Cookies; + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + +
+ +
+
+
+ +@code { + + private readonly Error _error = new() + { + UserMessage = string.Empty + }; + + readonly BotBroadcastModel _model = new(); + readonly Message _message = new() + { + Uid = Guid.NewGuid().ToString(), + Attachments = [] + }; + + private bool _uploadEnabled = true; + private int _uploadProgress = 0; + + [Parameter] + public string? BotId { get; set; } + + protected override bool ShouldRender() + { + return true; + } + + protected override Task OnInitializedAsync() + { + if (BotId == null) + UriHelper.NavigateTo("/your_bots", true); + else + _model.BotId = BotId; + + return Task.CompletedTask; + } + + private async Task OnSubmit(BotBroadcastModel model) + { + var sessionToken = await Cookies.GetValueAsync("SessionToken"); + _message.Body = model.Message; + if (string.IsNullOrWhiteSpace(sessionToken)) return; + + Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", sessionToken); + + if (!_message.Attachments.Any()) + { + var content = new StringContent(JsonSerializer.Serialize(_message), + Encoding.UTF8, + "application/json"); + + var response = await Http.PostAsync(Url.Combine(BackSettings.CurrentValue.BackUrl, $"/admin/SendBroadcast?botId={BotId}"), + content); + +#if DEBUG + if (!response.IsSuccessStatusCode) _error.UserMessage = $"Error sending a broadcast message: {response.ReasonPhrase}"; + Console.WriteLine(_error.UserMessage); +#endif + } + else + { + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, _message); + + var progressStreamContent = new ProgressStreamContent(ms, + (totalRead, + totalBytes) => + { + _uploadProgress = (int)((double)totalRead / totalBytes); + Console.WriteLine($"UploadProgress: {_uploadProgress}"); + }); + + progressStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + + var binaryResponse = await Http.PostAsync(Url.Combine(BackSettings.CurrentValue.BackUrl, $"/admin/SendBroadcastBinary?botId={BotId}"), + progressStreamContent); + +#if DEBUG + if (!binaryResponse.IsSuccessStatusCode) _error.UserMessage = $"Error sending a broadcast message (binary): {binaryResponse.ReasonPhrase}"; + Console.WriteLine(_error.UserMessage); +#endif + } + + UriHelper.NavigateTo("/your_bots", true); + } + + private async Task OnFileChange(UploadChangeEventArgs? fileChange) + { + if (fileChange == null) return; + + foreach (var file in fileChange.Files) + { + try + { + if (file?.ContentType == null!) continue; + + var mediaType = MimeTypeConverter.ConvertMimeTypeToMediaType(file.ContentType); + if (mediaType == MediaType.Unknown) continue; + + var stream = file.OpenReadStream(Constants.MaxAllowedFileSize); + var bytes = await stream.FromStreamToBytesAsync(); + var content = new BinaryBaseAttachment(Guid.NewGuid().ToString(), + file.Name, + mediaType, + string.Empty, + bytes); + + _message.Attachments.Add(content); + + _uploadEnabled = false; + } + catch (Exception ex) + { + Console.WriteLine($"OnFileChange: exception {ex.Message}"); + } + } + } + + private Task OnProgress(UploadProgressArgs arg) + { + _uploadProgress = arg.Progress; + + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/Botticelli.Server.FrontNew/Pages/Login.razor b/Botticelli.Server.FrontNew/Pages/Login.razor index 4613563d..87d8b3f5 100644 --- a/Botticelli.Server.FrontNew/Pages/Login.razor +++ b/Botticelli.Server.FrontNew/Pages/Login.razor @@ -32,6 +32,11 @@ Login + + + + + diff --git a/Botticelli.Server.FrontNew/Pages/Logoff.razor b/Botticelli.Server.FrontNew/Pages/Logoff.razor index cbb1596d..f3f579e7 100644 --- a/Botticelli.Server.FrontNew/Pages/Logoff.razor +++ b/Botticelli.Server.FrontNew/Pages/Logoff.razor @@ -10,6 +10,11 @@ Logoff + + + + + diff --git a/Botticelli.Server.FrontNew/Pages/Register.razor b/Botticelli.Server.FrontNew/Pages/Register.razor index aa32bbbd..42cfbb4d 100644 --- a/Botticelli.Server.FrontNew/Pages/Register.razor +++ b/Botticelli.Server.FrontNew/Pages/Register.razor @@ -22,6 +22,12 @@ Register User + + + + + + diff --git a/Botticelli.Server.FrontNew/Pages/UpdateBot.razor b/Botticelli.Server.FrontNew/Pages/UpdateBot.razor index 436d8c46..3e8f906c 100644 --- a/Botticelli.Server.FrontNew/Pages/UpdateBot.razor +++ b/Botticelli.Server.FrontNew/Pages/UpdateBot.razor @@ -71,14 +71,14 @@ var sessionToken = await Cookies.GetValueAsync("SessionToken"); var bot = await GetBot(sessionToken); - var botStatus = await GetBotStatus(bot, sessionToken); + var botStatus = await GetBotStatus(sessionToken); _model.BotId = BotId; _model.BotName = bot.BotName; _model.BotKey = botStatus.BotContext.BotKey; } - private async Task GetBotStatus(BotInfo? bot, string sessionToken) + private async Task GetBotStatus(string sessionToken) { using var http = HttpClientFactory.Create(); http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", sessionToken); diff --git a/Botticelli.Server.FrontNew/Pages/YourBots.razor b/Botticelli.Server.FrontNew/Pages/YourBots.razor index de8c1017..3abaaddf 100644 --- a/Botticelli.Server.FrontNew/Pages/YourBots.razor +++ b/Botticelli.Server.FrontNew/Pages/YourBots.razor @@ -11,12 +11,7 @@ @inject CookieStorageAccessor Cookies; @inject IJSRuntime JsRuntime; -@code { - private string _error = string.Empty; -} - Your bots -

Bot list

@if (_bots == null) @@ -44,6 +39,10 @@ else + - @**@ - ("confirm", new object[] {"Are you sure?"})) return; + if (!await JsRuntime.InvokeAsync("confirm", ["Are you sure?"])) return; await _http.GetAsync(Url.Combine(BackSettings.CurrentValue.BackUrl, $"/admin/RemoveBot?botId={botId}")); UriHelper.NavigateTo("/your_bots", true); diff --git a/Botticelli.Server.FrontNew/Shared/MainLayout.razor b/Botticelli.Server.FrontNew/Shared/MainLayout.razor index f6943e56..eac5e2a0 100644 --- a/Botticelli.Server.FrontNew/Shared/MainLayout.razor +++ b/Botticelli.Server.FrontNew/Shared/MainLayout.razor @@ -7,7 +7,7 @@
diff --git a/Botticelli.Server.FrontNew/Shared/NavMenu.razor b/Botticelli.Server.FrontNew/Shared/NavMenu.razor index e0b1dc25..76ffe6f6 100644 --- a/Botticelli.Server.FrontNew/Shared/NavMenu.razor +++ b/Botticelli.Server.FrontNew/Shared/NavMenu.razor @@ -1,6 +1,7 @@ @using Botticelli.Server.FrontNew.Clients @inject CookieStorageAccessor Cookies; @inject SessionClient SessionClient; +@inject NavigationManager UriHelper; @code { private bool _collapseNavMenu = true; @@ -16,6 +17,11 @@ _collapseNavMenu = !_collapseNavMenu; } + public void Refresh() + { + UriHelper.Refresh(); + } + /// /// Method invoked when the component is ready to start, having received its /// initial parameters from its parent in the render tree. @@ -58,7 +64,8 @@ { @@ -67,17 +74,18 @@ { + } - - } diff --git a/Botticelli.Server.FrontNew/Utils/Constants.cs b/Botticelli.Server.FrontNew/Utils/Constants.cs new file mode 100644 index 00000000..574de764 --- /dev/null +++ b/Botticelli.Server.FrontNew/Utils/Constants.cs @@ -0,0 +1,6 @@ +namespace Botticelli.Server.FrontNew.Utils; + +public class Constants +{ + internal const int MaxAllowedFileSize = 104857600; +} \ No newline at end of file diff --git a/Botticelli.Server.FrontNew/Utils/ProgressStreamContent.cs b/Botticelli.Server.FrontNew/Utils/ProgressStreamContent.cs new file mode 100644 index 00000000..c2db5d9a --- /dev/null +++ b/Botticelli.Server.FrontNew/Utils/ProgressStreamContent.cs @@ -0,0 +1,25 @@ +using System.Net; + +namespace Botticelli.Server.FrontNew.Utils; + +public class ProgressStreamContent(Stream content, Action progress) : StreamContent(content) +{ + private const int BufferSize = 8192; + + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) + { + var totalBytes = Headers.ContentLength ?? -1; + var buffer = new byte[BufferSize]; + long totalRead = 0; + int bytesRead; + + var contentStream = await ReadAsStreamAsync(); + contentStream.Seek(0, SeekOrigin.Begin); + while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0) + { + await stream.WriteAsync(buffer.AsMemory(0, bytesRead)); + totalRead += bytesRead; + progress?.Invoke(totalRead, totalBytes); + } + } +} \ No newline at end of file diff --git a/Botticelli.Server.FrontNew/wwwroot/Viber.png b/Botticelli.Server.FrontNew/wwwroot/Viber.png deleted file mode 100644 index ca4f9f53..00000000 Binary files a/Botticelli.Server.FrontNew/wwwroot/Viber.png and /dev/null differ diff --git a/Botticelli.Server.FrontNew/wwwroot/index.html b/Botticelli.Server.FrontNew/wwwroot/index.html index 9f112ba7..e7cec476 100644 --- a/Botticelli.Server.FrontNew/wwwroot/index.html +++ b/Botticelli.Server.FrontNew/wwwroot/index.html @@ -13,7 +13,9 @@ -
Wait, please...
+
+ Wait, please... +
An unhandled error has occurred. diff --git a/Botticelli.Server.FrontNew/wwwroot/new_logo_m.png b/Botticelli.Server.FrontNew/wwwroot/new_logo_m.png new file mode 100644 index 00000000..81bd061b Binary files /dev/null and b/Botticelli.Server.FrontNew/wwwroot/new_logo_m.png differ diff --git a/Botticelli.Shared/API/Admin/Responses/BotStatus.cs b/Botticelli.Shared/API/Admin/Responses/BotStatus.cs index 4a67dcc3..aa84e70c 100644 --- a/Botticelli.Shared/API/Admin/Responses/BotStatus.cs +++ b/Botticelli.Shared/API/Admin/Responses/BotStatus.cs @@ -4,5 +4,6 @@ public enum BotStatus { Unlocked, Locked, - Unknown + Unknown, + Error } \ No newline at end of file diff --git a/Botticelli.Shared/API/Client/Requests/DeleteMessageRequest.cs b/Botticelli.Shared/API/Client/Requests/DeleteMessageRequest.cs new file mode 100644 index 00000000..e75eb15a --- /dev/null +++ b/Botticelli.Shared/API/Client/Requests/DeleteMessageRequest.cs @@ -0,0 +1,11 @@ +namespace Botticelli.Shared.API.Client.Requests; + +public class DeleteMessageRequest : BaseRequest +{ + public DeleteMessageRequest(string? uid, string chatId) : base(uid) + { + ChatId = chatId; + } + + public string? ChatId { get; set; } +} \ No newline at end of file diff --git a/Botticelli.Shared/API/Client/Requests/RemoveMessageRequest.cs b/Botticelli.Shared/API/Client/Requests/RemoveMessageRequest.cs deleted file mode 100644 index 16e44757..00000000 --- a/Botticelli.Shared/API/Client/Requests/RemoveMessageRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Botticelli.Shared.API; - -public class RemoveMessageRequest : BaseRequest -{ - public RemoveMessageRequest(string? uid, string chatId) : base(uid) - { - ChatId = chatId; - } - - public string? ChatId { get; set; } -} \ No newline at end of file diff --git a/Botticelli.Shared/API/Client/Responses/GetRequiredStatusFromServerResponse.cs b/Botticelli.Shared/API/Client/Responses/GetRequiredStatusFromServerResponse.cs index e812e6d3..7b025c0a 100644 --- a/Botticelli.Shared/API/Client/Responses/GetRequiredStatusFromServerResponse.cs +++ b/Botticelli.Shared/API/Client/Responses/GetRequiredStatusFromServerResponse.cs @@ -8,5 +8,5 @@ public class GetRequiredStatusFromServerResponse : ServerBaseResponse public required string BotId { get; set; } public BotStatus? Status { get; set; } - public required BotContext BotContext { get; set; } + public required BotContext? BotContext { get; set; } } \ No newline at end of file diff --git a/Botticelli.Shared/Botticelli.Shared.csproj b/Botticelli.Shared/Botticelli.Shared.csproj index 27705e02..af2ae641 100644 --- a/Botticelli.Shared/Botticelli.Shared.csproj +++ b/Botticelli.Shared/Botticelli.Shared.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli @@ -21,7 +21,7 @@ - + \ No newline at end of file diff --git a/Botticelli.Shared/Constants/MimeTypeConverter.cs b/Botticelli.Shared/Constants/MimeTypeConverter.cs new file mode 100644 index 00000000..e7ff0811 --- /dev/null +++ b/Botticelli.Shared/Constants/MimeTypeConverter.cs @@ -0,0 +1,22 @@ +namespace Botticelli.Shared.Constants; + +public static class MimeTypeConverter +{ + public static MediaType ConvertMimeTypeToMediaType(string mimeType) + { + if (string.IsNullOrEmpty(mimeType)) return MediaType.Unknown; + + switch (mimeType.ToLowerInvariant()) + { + case var _ when mimeType.StartsWith("audio/"): + case "application/ogg": return MediaType.Audio; + case var _ when mimeType.StartsWith("video/"): return MediaType.Video; + case var _ when mimeType.StartsWith("text/"): return MediaType.Text; + case var _ when mimeType.StartsWith("image/"): return MediaType.Image; + case var _ when mimeType.StartsWith("application/"): + case var _ when mimeType.StartsWith("multipart/"): + return MediaType.Document; + default: return MediaType.Unknown; + } + } +} \ No newline at end of file diff --git a/Botticelli.Shared/Utils/Assertions.cs b/Botticelli.Shared/Utils/Assertions.cs index 8dbc8832..a902af52 100644 --- a/Botticelli.Shared/Utils/Assertions.cs +++ b/Botticelli.Shared/Utils/Assertions.cs @@ -16,7 +16,7 @@ public static void NotNullOrEmpty(this IEnumerable input) public static IEnumerable EmptyIfNull(this IEnumerable? input) { - return (input is null ? input : Array.Empty())!; + return (input is null ? input : [])!; } public static string EmptyIfNull(this string? input) diff --git a/Botticelli.Shared/Utils/StreamUtils.cs b/Botticelli.Shared/Utils/StreamUtils.cs index 704eab74..1e290d16 100644 --- a/Botticelli.Shared/Utils/StreamUtils.cs +++ b/Botticelli.Shared/Utils/StreamUtils.cs @@ -10,10 +10,38 @@ public static Stream ToStream(this byte[] input) return stream; } - public static string FromStream(this Stream input) + public static string FromStreamToString(this Stream input) { + CheckForNull(input); + using var sr = new StreamReader(input); return sr.ReadToEnd(); } + + public static byte[] FromStreamToBytes(this Stream input) + { + CheckForNull(input); + + using var memoryStream = new MemoryStream(); + input.CopyTo(memoryStream); + + return memoryStream.ToArray(); + } + + public static async Task FromStreamToBytesAsync(this Stream input) + { + CheckForNull(input); + + using var memoryStream = new MemoryStream(); + await input.CopyToAsync(memoryStream); + + return memoryStream.ToArray(); + } + + private static void CheckForNull(Stream input) + { + if (input == null) + throw new ArgumentNullException(nameof(input), "Stream cannot be null."); + } } \ No newline at end of file diff --git a/Botticelli.Shared/ValueObjects/Message.cs b/Botticelli.Shared/ValueObjects/Message.cs index d17ee2fb..e6cc7941 100644 --- a/Botticelli.Shared/ValueObjects/Message.cs +++ b/Botticelli.Shared/ValueObjects/Message.cs @@ -4,7 +4,7 @@ /// Received/sent message ///
[Serializable] -public class Message +public class Message() { /// /// Type of message @@ -27,14 +27,6 @@ public enum MessageType Extended } - public Message() - { - ChatIds = []; - Uid = Guid.NewGuid().ToString(); - CreatedAt = DateTime.Now; - ProcessingArgs = new List(1); - } - public Message(string uid) : this() { Uid = uid; @@ -48,7 +40,7 @@ public Message(string uid) : this() /// /// Message uid /// - public string? Uid { get; set; } + public string? Uid { get; set; } = Guid.NewGuid().ToString(); /// /// Chat Id <=> Inner message id links @@ -58,7 +50,7 @@ public Message(string uid) : this() /// /// Chat ids /// - public List ChatIds { get; set; } + public List ChatIds { get; set; } = []; /// /// Message subj @@ -73,12 +65,12 @@ public Message(string uid) : this() /// /// Message arguments for processing /// - public IList? ProcessingArgs { get; set; } + public IList? ProcessingArgs { get; set; } = new List(1); /// /// Message attachments /// - public List? Attachments { get; set; } + public List Attachments { get; set; } = []; /// /// From user @@ -118,7 +110,7 @@ public Message(string uid) : this() /// /// Message creation date /// - public DateTime CreatedAt { get; init; } + public DateTime CreatedAt { get; init; } = DateTime.Now; /// /// Message modification date diff --git a/Botticelli.Talks/BaseTtsSpeaker.cs b/Botticelli.Talks/BaseTtsSpeaker.cs index 76707b67..0c0ab26f 100644 --- a/Botticelli.Talks/BaseTtsSpeaker.cs +++ b/Botticelli.Talks/BaseTtsSpeaker.cs @@ -63,18 +63,16 @@ protected async Task Compress(byte[] input, CancellationToken token) using var bufferStream = new MemoryStream(input); await using var wavReader = new WaveFileReader(bufferStream); - await using (var mp3Writer = new LameMP3FileWriter(resultStream, wavReader.WaveFormat, preset)) - { - await wavReader.CopyToAsync(mp3Writer, token); + await using var mp3Writer = new LameMP3FileWriter(resultStream, wavReader.WaveFormat, preset); + await wavReader.CopyToAsync(mp3Writer, token); - return resultStream.ToArray(); - } + return resultStream.ToArray(); } catch (Exception ex) { - Logger.LogError($"Error while compressing: {ex.Message}", ex); + Logger.LogError("Error while compressing: {ExMessage} {ex}", ex.Message, ex); } - return Array.Empty(); + return []; } } \ No newline at end of file diff --git a/Botticelli.Talks/Botticelli.Talks.csproj b/Botticelli.Talks/Botticelli.Talks.csproj index a1bf21f7..81163ed8 100644 --- a/Botticelli.Talks/Botticelli.Talks.csproj +++ b/Botticelli.Talks/Botticelli.Talks.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli.Talks BotticelliBots new_logo_compact.png @@ -32,9 +32,7 @@ - - new_logo_compact.png - + \ No newline at end of file diff --git a/Botticelli.Talks/OpenTts/OpenTtsSpeaker.cs b/Botticelli.Talks/OpenTts/OpenTtsSpeaker.cs index bc089ca5..d420a585 100644 --- a/Botticelli.Talks/OpenTts/OpenTtsSpeaker.cs +++ b/Botticelli.Talks/OpenTts/OpenTtsSpeaker.cs @@ -42,9 +42,9 @@ public override async Task Speak(string markedText, if (!result.IsSuccessStatusCode) { - Logger.LogError($"Can't get response from voice: {result.StatusCode}: {result.ReasonPhrase}!"); + Logger.LogError("Can't get response from voice: {ResultStatusCode}: {ResultReasonPhrase}!", result.StatusCode, result.ReasonPhrase); - return Array.Empty(); + return []; } var byteResult = await result.Content.ReadAsByteArrayAsync(token); diff --git a/Botticelli.WeChat/Botticelli.WeChat.csproj b/Botticelli.WeChat/Botticelli.WeChat.csproj deleted file mode 100644 index 9af7d037..00000000 --- a/Botticelli.WeChat/Botticelli.WeChat.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net8.0 - enable - enable - - - diff --git a/Botticelli.WeChat/Program.cs b/Botticelli.WeChat/Program.cs deleted file mode 100644 index ec8e4f41..00000000 --- a/Botticelli.WeChat/Program.cs +++ /dev/null @@ -1,6 +0,0 @@ -var builder = WebApplication.CreateBuilder(args); -var app = builder.Build(); - -app.MapGet("/", () => "Hello World!"); - -app.Run(); \ No newline at end of file diff --git a/Botticelli.WeChat/Properties/launchSettings.json b/Botticelli.WeChat/Properties/launchSettings.json deleted file mode 100644 index 8e3d0ec4..00000000 --- a/Botticelli.WeChat/Properties/launchSettings.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:43410", - "sslPort": 44380 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5225", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7237;http://localhost:5225", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/Botticelli.WeChat/appsettings.Development.json b/Botticelli.WeChat/appsettings.Development.json deleted file mode 100644 index 0c208ae9..00000000 --- a/Botticelli.WeChat/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/Botticelli.WeChat/appsettings.json b/Botticelli.WeChat/appsettings.json deleted file mode 100644 index 10f68b8c..00000000 --- a/Botticelli.WeChat/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/Botticelli.sln b/Botticelli.sln index 2f9d8801..71740ae0 100644 --- a/Botticelli.sln +++ b/Botticelli.sln @@ -23,8 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{346A18CA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Framework.Telegram", "Botticelli.Framework.Telegram\Botticelli.Framework.Telegram.csproj", "{71008870-2212-4A74-8777-B5B268C27911}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Viber.Api", "Viber.Api\Viber.Api.csproj", "{5EF8192E-6AEC-4FEF-A104-FEA0E7EA2DCE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Server.Data", "Botticelli.Server.Data\Botticelli.Server.Data.csproj", "{CDFC4EAD-60BD-419A-ADF5-451766D22F29}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Talks", "Botticelli.Talks\Botticelli.Talks.csproj", "{9F4B5D3F-661C-433B-BC9F-CAC4048EF9EA}" @@ -112,14 +110,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Scheduler", "Bot BotticelliSchedulerDescription.txt = BotticelliSchedulerDescription.txt EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Framework.Vk.Messages", "Botticelli.Framework.Vk\Botticelli.Framework.Vk.Messages.csproj", "{343922EC-2CF3-4C0E-B745-08F2B333B4E3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Framework.Vk.Tests", "Tests\Botticelli.Framework.Vk.Tests\Botticelli.Framework.Vk.Tests.csproj", "{2EE26ED9-C949-48E8-BF48-93EEC9E51AF1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VK", "VK", "{5BB351E1-460B-48C1-912C-E13702968BB2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ai.Messaging.Sample.Vk", "Samples\Ai.Messaging.Sample.Vk\Ai.Messaging.Sample.Vk.csproj", "{B610D900-6713-4D8F-8E9D-23AA02F846FF}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Audio", "Botticelli.Audio\Botticelli.Audio.csproj", "{0B5AB7F0-080D-49F3-A564-CE22F6F808AB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Audio.Tests", "Tests\Botticelli.Audio.Tests\Botticelli.Audio.Tests.csproj", "{868ED8BB-D451-42F3-AB3C-EE76AA9F483E}" @@ -140,8 +130,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Messaging.Sample.Common", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ai.Common.Sample", "Samples\Ai.Common.Sample\Ai.Common.Sample.csproj", "{8A5ECE73-1529-4B53-8907-735ADA05C20A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ai.ChatGpt.Sample.Vk", "Samples\Ai.ChatGpt.Sample.Vk\Ai.ChatGpt.Sample.Vk.csproj", "{AAD10628-7441-419D-8FE8-F37BF8FB4681}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Framework.Events", "Botticelli.Framework.Common\Botticelli.Framework.Events.csproj", "{B0863634-2EE0-4F24-898F-6FB0154E3EB5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Botticelli.Bot.Utils", "Botticelli.Bot.Utils\Botticelli.Bot.Utils.csproj", "{E843665C-F541-4915-838D-346150389C36}" @@ -161,8 +149,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Messaging.Sample.Telegram", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ai.YaGpt.Sample.Telegram", "Samples\Ai.YaGpt.Sample.Telegram\Ai.YaGpt.Sample.Telegram.csproj", "{D44E775F-7474-4F49-8633-384DC87BE40B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ai.YaGpt.Sample.Vk", "Samples\Ai.YaGpt.Sample.Vk\Ai.YaGpt.Sample.Vk.csproj", "{20AE866A-89D1-4C72-ADAC-545E44AB1354}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.AI", "Botticelli.AI", "{3B5A6BD8-2B94-42F9-B122-06D92C470B31}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.AI.ChatGpt", "Botticelli.AI.ChatGpt\Botticelli.AI.ChatGpt.csproj", "{D92C29CF-581E-425B-B96B-01D5EFDA1614}" @@ -175,25 +161,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.AI.GptJ", "Botti EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ai.DeepSeek.Sample.Telegram", "Samples\Ai.DeepSeek.Sample.Telegram\Ai.DeepSeek.Sample.Telegram.csproj", "{3D52F777-3A84-4238-974F-A5969085B472}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.WeChat", "Botticelli.WeChat\Botticelli.WeChat.csproj", "{B8EE61E9-F029-49EA-81E5-D0BCBA265418}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{1BBE72D9-3509-4750-B79E-766B3122B999}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{94539590-4B6F-4500-B010-DED4D4A30529}" - ProjectSection(SolutionItems) = preProject - deploy\linux\bot_sample\Dockerfile = deploy\linux\bot_sample\Dockerfile - deploy\linux\bot_sample\push_docker.sh = deploy\linux\bot_sample\push_docker.sh - EndProjectSection -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Scheduler.Hangfire", "Botticelli.Scheduler.Hangfire\Botticelli.Scheduler.Hangfire.csproj", "{85C84F76-3730-4D17-923E-96B665544B57}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Scheduler.Quartz", "Botticelli.Scheduler.Quartz\Botticelli.Scheduler.Quartz.csproj", "{F958B922-0418-4AB8-B5C6-86510DB52F38}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Framework.Controls", "Botticelli.Framework.Controls\Botticelli.Framework.Controls.csproj", "{C556B6EE-DCE7-4ED2-85E1-5D5212E0C184}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Controls", "Botticelli.Controls\Botticelli.Controls.csproj", "{C556B6EE-DCE7-4ED2-85E1-5D5212E0C184}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Framework.Controls.Tests", "Tests\Botticelli.Framework.Controls.Tests\Botticelli.Framework.Controls.Tests.csproj", "{70EB3FE2-9EB5-4B62-9405-71D1A332B0FD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Controls.Tests", "Tests\Botticelli.Controls.Tests\Botticelli.Controls.Tests.csproj", "{70EB3FE2-9EB5-4B62-9405-71D1A332B0FD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Framework.Controls.Layouts", "Botticelli.Framework.Controls.Layouts\Botticelli.Framework.Controls.Layouts.csproj", "{C693D17D-DEB1-43E9-9EE4-031D14696065}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Controls.Layouts", "Botticelli.Controls.Layouts\Botticelli.Controls.Layouts.csproj", "{C693D17D-DEB1-43E9-9EE4-031D14696065}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Layouts.Sample.Telegram", "Samples\Layouts.Sample.Telegram\Layouts.Sample.Telegram.csproj", "{2DF28873-A172-42EB-8FBD-324DF8AD9323}" EndProject @@ -201,7 +177,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Locations", "Bot EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Locations.Tests", "Botticelli.Locations.Tests\Botticelli.Locations.Tests.csproj", "{9B731750-D28D-4DE0-AC8E-280E932BD301}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Tests\Shared\Shared.csproj", "{00F8B86E-B3DC-413C-AA74-D3EB747FFCF4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mocks", "Tests\Mocks\Mocks.csproj", "{00F8B86E-B3DC-413C-AA74-D3EB747FFCF4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Controls", "Botticelli.Controls", "{B71A5B64-3A0D-4FD6-9BB3-C2EAEA15B60F}" EndProject @@ -209,8 +185,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Location", "Bott EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Locations.Telegram", "Botticelli.Locations.Telegram\Botticelli.Locations.Telegram.csproj", "{3585C2D1-9AA8-4B83-918D-588D701E37F5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Locations.Vk", "Botticelli.Locations.Vk\Botticelli.Locations.Vk.csproj", "{89865D7E-8769-477B-8CED-C5B53C981178}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandChain.Sample.Telegram", "Samples\CommandChain.Sample.Telegram\CommandChain.Sample.Telegram.csproj", "{E33D4F38-48D2-44C7-A56B-4CE080A5B593}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.AI.Test", "Botticelli.AI.Test\Botticelli.AI.Test.csproj", "{332A0B08-B1C9-4321-AD31-B2102B95AFEA}" @@ -221,7 +195,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Bot.Data.Entitie EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monads.Sample.Telegram", "Samples\Monads.Sample.Telegram\Monads.Sample.Telegram.csproj", "{CBD02578-B208-4888-AB18-D9969A4082ED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Framework.Monads", "Botticelli.Framework.Monads\Botticelli.Framework.Monads.csproj", "{D159BBC6-8D73-44AB-A17B-B14AB7D56A0D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Chained.Monads", "Botticelli.Chained.Monads\Botticelli.Chained.Monads.csproj", "{D159BBC6-8D73-44AB-A17B-B14AB7D56A0D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI", "AI", "{8300ACD9-51DA-4B69-B7DF-FB2E5CC63F73}" EndProject @@ -251,6 +225,46 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auth.Sample.Telegram", "Sam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Auth.Data.Sqlite", "Botticelli.Auth.Data.Sqlite\Botticelli.Auth.Data.Sqlite.csproj", "{D27A078E-1409-4B0B-AD7E-DD4528206049}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messaging.Sample.Telegram.Standalone", "Messaging.Sample.Telegram.Standalone\Messaging.Sample.Telegram.Standalone.csproj", "{E9B52BA7-B0B3-4823-9BB3-8B4AA40CB2C8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Chained", "Botticelli.Chained", "{DB9DA043-4E18-433B-BD71-2CCC8A5E28C0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Bus", "Botticelli.Bus", "{EF3D6AA7-0334-4ECC-A1FD-C68856B7B954}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Pay", "Botticelli.Pay", "{34F34B4D-5BA2-45EC-8BC0-2507AF50A9A5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Scheduler", "Botticelli.Scheduler", "{2CC72293-C3FB-4A01-922A-E8C7CCD689A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Chained.Context", "Botticelli.Chained.Context\Botticelli.Chained.Context.csproj", "{3A9F8D81-DB62-43D3-841C-9F37D8238E36}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Chained.Context.InMemory", "Botticelli.Chained.Context.InMemory\Botticelli.Chained.Context.InMemory.csproj", "{1A8FB2E2-4272-402F-9301-9C75FEA2E9C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Chained.Context.Redis", "Botticelli.Chained.Context.Redis\Botticelli.Chained.Context.Redis.csproj", "{4E5352E6-9F06-4A92-A6D9-E82DBA516E72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Chained.Context.InMemory.Tests", "Tests\Botticelli.Chained.Context.InMemory.Tests\Botticelli.Chained.Context.InMemory.Tests.csproj", "{D9752FF9-AC66-4CC9-827E-65F16868BF0E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Chained.Context.Redis.Tests", "Tests\Botticelli.Chained.Context.Redis.Tests\Botticelli.Chained.Context.Redis.Tests.csproj", "{2FEF33B2-11B2-4D6A-B568-69FCDF6F098C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Chained", "Botticelli.Chained", "{0D3F6F1E-D5A5-4E6A-8140-4FE03601E084}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Botticelli.Broadcasting", "Botticelli.Broadcasting", "{39412AE4-B325-4827-B24E-C15415DCA7E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Broadcasting", "Botticelli.Broadcasting\Botticelli.Broadcasting.csproj", "{88039BCA-7501-48C8-9F94-9F8C2C0A19A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Broadcasting.Dal", "Botticelli.Broadcasting.Dal\Botticelli.Broadcasting.Dal.csproj", "{9F898466-739B-4DE0-BFD7-6B5203957236}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Broadcasting.Shared", "Botticelli.Broadcasting.Shared\Botticelli.Broadcasting.Shared.csproj", "{0F1EE8FF-4CE4-49D5-BF49-3837EB3BD637}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messengers", "Messengers", "{9D2CCAB5-67AB-4A1A-B329-77F7973DB2A2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Broadcasting", "Broadcasting", "{4B590BAD-B92D-427C-B340-68A40A9C9B95}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Broadcasting.Sample.Telegram", "Samples\Broadcasting.Sample.Telegram\Broadcasting.Sample.Telegram.csproj", "{2E48B176-D743-49B3-B1F2-6EC159EB80EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Broadcasting.Sample.Common", "Samples\Broadcasting.Sample.Common\Broadcasting.Sample.Common.csproj", "{F0B6FF75-499F-463D-A39E-AB39BFAC0F8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botticelli.Broadcasting.Telegram", "Botticelli.Broadcasting.Telegram\Botticelli.Broadcasting.Telegram.csproj", "{B64F1EA1-9AEA-4828-BC8B-363FDFD8210F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -277,10 +291,6 @@ Global {71008870-2212-4A74-8777-B5B268C27911}.Debug|Any CPU.Build.0 = Debug|Any CPU {71008870-2212-4A74-8777-B5B268C27911}.Release|Any CPU.ActiveCfg = Release|Any CPU {71008870-2212-4A74-8777-B5B268C27911}.Release|Any CPU.Build.0 = Release|Any CPU - {5EF8192E-6AEC-4FEF-A104-FEA0E7EA2DCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5EF8192E-6AEC-4FEF-A104-FEA0E7EA2DCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5EF8192E-6AEC-4FEF-A104-FEA0E7EA2DCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5EF8192E-6AEC-4FEF-A104-FEA0E7EA2DCE}.Release|Any CPU.Build.0 = Release|Any CPU {CDFC4EAD-60BD-419A-ADF5-451766D22F29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CDFC4EAD-60BD-419A-ADF5-451766D22F29}.Debug|Any CPU.Build.0 = Debug|Any CPU {CDFC4EAD-60BD-419A-ADF5-451766D22F29}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -325,18 +335,6 @@ Global {2423A645-22D1-4736-946F-10A99353581C}.Debug|Any CPU.Build.0 = Debug|Any CPU {2423A645-22D1-4736-946F-10A99353581C}.Release|Any CPU.ActiveCfg = Release|Any CPU {2423A645-22D1-4736-946F-10A99353581C}.Release|Any CPU.Build.0 = Release|Any CPU - {343922EC-2CF3-4C0E-B745-08F2B333B4E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {343922EC-2CF3-4C0E-B745-08F2B333B4E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {343922EC-2CF3-4C0E-B745-08F2B333B4E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {343922EC-2CF3-4C0E-B745-08F2B333B4E3}.Release|Any CPU.Build.0 = Release|Any CPU - {2EE26ED9-C949-48E8-BF48-93EEC9E51AF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2EE26ED9-C949-48E8-BF48-93EEC9E51AF1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2EE26ED9-C949-48E8-BF48-93EEC9E51AF1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2EE26ED9-C949-48E8-BF48-93EEC9E51AF1}.Release|Any CPU.Build.0 = Release|Any CPU - {B610D900-6713-4D8F-8E9D-23AA02F846FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B610D900-6713-4D8F-8E9D-23AA02F846FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B610D900-6713-4D8F-8E9D-23AA02F846FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B610D900-6713-4D8F-8E9D-23AA02F846FF}.Release|Any CPU.Build.0 = Release|Any CPU {0B5AB7F0-080D-49F3-A564-CE22F6F808AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B5AB7F0-080D-49F3-A564-CE22F6F808AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {0B5AB7F0-080D-49F3-A564-CE22F6F808AB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -369,10 +367,6 @@ Global {8A5ECE73-1529-4B53-8907-735ADA05C20A}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A5ECE73-1529-4B53-8907-735ADA05C20A}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A5ECE73-1529-4B53-8907-735ADA05C20A}.Release|Any CPU.Build.0 = Release|Any CPU - {AAD10628-7441-419D-8FE8-F37BF8FB4681}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAD10628-7441-419D-8FE8-F37BF8FB4681}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAD10628-7441-419D-8FE8-F37BF8FB4681}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAD10628-7441-419D-8FE8-F37BF8FB4681}.Release|Any CPU.Build.0 = Release|Any CPU {B0863634-2EE0-4F24-898F-6FB0154E3EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B0863634-2EE0-4F24-898F-6FB0154E3EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0863634-2EE0-4F24-898F-6FB0154E3EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -397,10 +391,6 @@ Global {D44E775F-7474-4F49-8633-384DC87BE40B}.Debug|Any CPU.Build.0 = Debug|Any CPU {D44E775F-7474-4F49-8633-384DC87BE40B}.Release|Any CPU.ActiveCfg = Release|Any CPU {D44E775F-7474-4F49-8633-384DC87BE40B}.Release|Any CPU.Build.0 = Release|Any CPU - {20AE866A-89D1-4C72-ADAC-545E44AB1354}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {20AE866A-89D1-4C72-ADAC-545E44AB1354}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20AE866A-89D1-4C72-ADAC-545E44AB1354}.Release|Any CPU.ActiveCfg = Release|Any CPU - {20AE866A-89D1-4C72-ADAC-545E44AB1354}.Release|Any CPU.Build.0 = Release|Any CPU {D92C29CF-581E-425B-B96B-01D5EFDA1614}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D92C29CF-581E-425B-B96B-01D5EFDA1614}.Debug|Any CPU.Build.0 = Debug|Any CPU {D92C29CF-581E-425B-B96B-01D5EFDA1614}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -421,10 +411,6 @@ Global {3D52F777-3A84-4238-974F-A5969085B472}.Debug|Any CPU.Build.0 = Debug|Any CPU {3D52F777-3A84-4238-974F-A5969085B472}.Release|Any CPU.ActiveCfg = Release|Any CPU {3D52F777-3A84-4238-974F-A5969085B472}.Release|Any CPU.Build.0 = Release|Any CPU - {B8EE61E9-F029-49EA-81E5-D0BCBA265418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B8EE61E9-F029-49EA-81E5-D0BCBA265418}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8EE61E9-F029-49EA-81E5-D0BCBA265418}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B8EE61E9-F029-49EA-81E5-D0BCBA265418}.Release|Any CPU.Build.0 = Release|Any CPU {85C84F76-3730-4D17-923E-96B665544B57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {85C84F76-3730-4D17-923E-96B665544B57}.Debug|Any CPU.Build.0 = Debug|Any CPU {85C84F76-3730-4D17-923E-96B665544B57}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -465,10 +451,6 @@ Global {3585C2D1-9AA8-4B83-918D-588D701E37F5}.Debug|Any CPU.Build.0 = Debug|Any CPU {3585C2D1-9AA8-4B83-918D-588D701E37F5}.Release|Any CPU.ActiveCfg = Release|Any CPU {3585C2D1-9AA8-4B83-918D-588D701E37F5}.Release|Any CPU.Build.0 = Release|Any CPU - {89865D7E-8769-477B-8CED-C5B53C981178}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89865D7E-8769-477B-8CED-C5B53C981178}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89865D7E-8769-477B-8CED-C5B53C981178}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89865D7E-8769-477B-8CED-C5B53C981178}.Release|Any CPU.Build.0 = Release|Any CPU {E33D4F38-48D2-44C7-A56B-4CE080A5B593}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E33D4F38-48D2-44C7-A56B-4CE080A5B593}.Debug|Any CPU.Build.0 = Debug|Any CPU {E33D4F38-48D2-44C7-A56B-4CE080A5B593}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -525,6 +507,54 @@ Global {D27A078E-1409-4B0B-AD7E-DD4528206049}.Debug|Any CPU.Build.0 = Debug|Any CPU {D27A078E-1409-4B0B-AD7E-DD4528206049}.Release|Any CPU.ActiveCfg = Release|Any CPU {D27A078E-1409-4B0B-AD7E-DD4528206049}.Release|Any CPU.Build.0 = Release|Any CPU + {E9B52BA7-B0B3-4823-9BB3-8B4AA40CB2C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9B52BA7-B0B3-4823-9BB3-8B4AA40CB2C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9B52BA7-B0B3-4823-9BB3-8B4AA40CB2C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9B52BA7-B0B3-4823-9BB3-8B4AA40CB2C8}.Release|Any CPU.Build.0 = Release|Any CPU + {3A9F8D81-DB62-43D3-841C-9F37D8238E36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A9F8D81-DB62-43D3-841C-9F37D8238E36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A9F8D81-DB62-43D3-841C-9F37D8238E36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A9F8D81-DB62-43D3-841C-9F37D8238E36}.Release|Any CPU.Build.0 = Release|Any CPU + {1A8FB2E2-4272-402F-9301-9C75FEA2E9C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A8FB2E2-4272-402F-9301-9C75FEA2E9C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A8FB2E2-4272-402F-9301-9C75FEA2E9C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A8FB2E2-4272-402F-9301-9C75FEA2E9C1}.Release|Any CPU.Build.0 = Release|Any CPU + {4E5352E6-9F06-4A92-A6D9-E82DBA516E72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E5352E6-9F06-4A92-A6D9-E82DBA516E72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E5352E6-9F06-4A92-A6D9-E82DBA516E72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E5352E6-9F06-4A92-A6D9-E82DBA516E72}.Release|Any CPU.Build.0 = Release|Any CPU + {D9752FF9-AC66-4CC9-827E-65F16868BF0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9752FF9-AC66-4CC9-827E-65F16868BF0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9752FF9-AC66-4CC9-827E-65F16868BF0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9752FF9-AC66-4CC9-827E-65F16868BF0E}.Release|Any CPU.Build.0 = Release|Any CPU + {2FEF33B2-11B2-4D6A-B568-69FCDF6F098C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FEF33B2-11B2-4D6A-B568-69FCDF6F098C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FEF33B2-11B2-4D6A-B568-69FCDF6F098C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FEF33B2-11B2-4D6A-B568-69FCDF6F098C}.Release|Any CPU.Build.0 = Release|Any CPU + {88039BCA-7501-48C8-9F94-9F8C2C0A19A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88039BCA-7501-48C8-9F94-9F8C2C0A19A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88039BCA-7501-48C8-9F94-9F8C2C0A19A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88039BCA-7501-48C8-9F94-9F8C2C0A19A5}.Release|Any CPU.Build.0 = Release|Any CPU + {9F898466-739B-4DE0-BFD7-6B5203957236}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F898466-739B-4DE0-BFD7-6B5203957236}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F898466-739B-4DE0-BFD7-6B5203957236}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F898466-739B-4DE0-BFD7-6B5203957236}.Release|Any CPU.Build.0 = Release|Any CPU + {0F1EE8FF-4CE4-49D5-BF49-3837EB3BD637}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F1EE8FF-4CE4-49D5-BF49-3837EB3BD637}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F1EE8FF-4CE4-49D5-BF49-3837EB3BD637}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F1EE8FF-4CE4-49D5-BF49-3837EB3BD637}.Release|Any CPU.Build.0 = Release|Any CPU + {2E48B176-D743-49B3-B1F2-6EC159EB80EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E48B176-D743-49B3-B1F2-6EC159EB80EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E48B176-D743-49B3-B1F2-6EC159EB80EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E48B176-D743-49B3-B1F2-6EC159EB80EF}.Release|Any CPU.Build.0 = Release|Any CPU + {F0B6FF75-499F-463D-A39E-AB39BFAC0F8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0B6FF75-499F-463D-A39E-AB39BFAC0F8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0B6FF75-499F-463D-A39E-AB39BFAC0F8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0B6FF75-499F-463D-A39E-AB39BFAC0F8D}.Release|Any CPU.Build.0 = Release|Any CPU + {B64F1EA1-9AEA-4828-BC8B-363FDFD8210F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B64F1EA1-9AEA-4828-BC8B-363FDFD8210F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B64F1EA1-9AEA-4828-BC8B-363FDFD8210F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B64F1EA1-9AEA-4828-BC8B-363FDFD8210F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -534,19 +564,13 @@ Global {2E0D3754-2B71-445F-9944-E07B79BCD804} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {B1EEED03-3863-4673-8690-8A89912AA865} = {41AA926F-88C1-4FC4-8F04-6FBA3CA6C741} {B620052D-DA8D-42F6-ACC8-6228B1840A23} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {71008870-2212-4A74-8777-B5B268C27911} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {5EF8192E-6AEC-4FEF-A104-FEA0E7EA2DCE} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {CDFC4EAD-60BD-419A-ADF5-451766D22F29} = {A05D5A20-E0D3-4844-8311-C98E8B67DA7F} {9F4B5D3F-661C-433B-BC9F-CAC4048EF9EA} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {C010219B-6ECA-429F-98BA-839F6BAC0D6B} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {79DEC257-916B-402D-BBB9-BF4E6518FF8F} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {2851D399-D071-421A-B5B1-AB24883F82E0} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {1EE16F3E-B208-4211-9C77-9A24037668AC} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {BC2069C7-4808-4BF4-8D90-0160A588AD43} = {A05D5A20-E0D3-4844-8311-C98E8B67DA7F} {81FE9855-9884-49A9-A10D-61E7B709865E} = {A05D5A20-E0D3-4844-8311-C98E8B67DA7F} {B7B4B68F-7086-49F2-B401-C76B4EF0B320} = {346A18CA-EB1F-483B-8BC3-EDF250DA72E6} {2AD59714-541D-4794-9216-6EAB25F271DA} = {35480478-646E-45F1-96A3-EE7AC498ACE3} - {FD291718-B989-4660-AE04-70F7A456B7BB} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {A4F5B449-60D4-4959-9E0B-5EECFD2A317B} = {0530F522-2556-4D69-8275-86947E9D51EF} {9B590747-74F6-41C8-8733-B5B77A8FCAC1} = {A4F5B449-60D4-4959-9E0B-5EECFD2A317B} {2423A645-22D1-4736-946F-10A99353581C} = {A05D5A20-E0D3-4844-8311-C98E8B67DA7F} @@ -562,34 +586,23 @@ Global {85DE8BC6-2A06-4088-B4AA-DD520CBC2369} = {912B05D1-DC22-411E-9D6E-E06FFE96E4E6} {184C8F8D-DC80-4C52-88F9-8AE799C51266} = {85DE8BC6-2A06-4088-B4AA-DD520CBC2369} {3DFA2DF1-6720-44D7-91E2-B50572F8A2E6} = {912B05D1-DC22-411E-9D6E-E06FFE96E4E6} - {343922EC-2CF3-4C0E-B745-08F2B333B4E3} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {2EE26ED9-C949-48E8-BF48-93EEC9E51AF1} = {B7B4B68F-7086-49F2-B401-C76B4EF0B320} - {5BB351E1-460B-48C1-912C-E13702968BB2} = {35480478-646E-45F1-96A3-EE7AC498ACE3} - {B610D900-6713-4D8F-8E9D-23AA02F846FF} = {5BB351E1-460B-48C1-912C-E13702968BB2} {0B5AB7F0-080D-49F3-A564-CE22F6F808AB} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {868ED8BB-D451-42F3-AB3C-EE76AA9F483E} = {346A18CA-EB1F-483B-8BC3-EDF250DA72E6} {AA4F8F66-D022-4E02-B03F-E7E101AF527A} = {1DBF2C91-4F7A-42EB-B5CC-C692F8167898} {B8AC3C63-EA2F-4D6A-9CF7-5935D787C4E3} = {1DBF2C91-4F7A-42EB-B5CC-C692F8167898} {DC84E739-621E-41F5-9250-0B961EE90B3E} = {1DBF2C91-4F7A-42EB-B5CC-C692F8167898} {6F38611B-7798-4514-91E3-B74A2FA228E4} = {8C52EB41-3815-4B12-9E40-C0D1472361CA} - {AAD10628-7441-419D-8FE8-F37BF8FB4681} = {5BB351E1-460B-48C1-912C-E13702968BB2} {B0863634-2EE0-4F24-898F-6FB0154E3EB5} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {E843665C-F541-4915-838D-346150389C36} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {255BE0A1-C453-466B-9BEF-11AFF0DBD2CF} = {346A18CA-EB1F-483B-8BC3-EDF250DA72E6} {17C89EE6-7845-4895-B945-05FC1134D680} = {255BE0A1-C453-466B-9BEF-11AFF0DBD2CF} {F3D3569B-39C7-427C-B123-EF268F0CB895} = {9B590747-74F6-41C8-8733-B5B77A8FCAC1} - {20AE866A-89D1-4C72-ADAC-545E44AB1354} = {5BB351E1-460B-48C1-912C-E13702968BB2} {3B5A6BD8-2B94-42F9-B122-06D92C470B31} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {F2270850-361A-4654-A893-718332560B02} = {3B5A6BD8-2B94-42F9-B122-06D92C470B31} {D92C29CF-581E-425B-B96B-01D5EFDA1614} = {3B5A6BD8-2B94-42F9-B122-06D92C470B31} {80F461AA-3C96-4AEA-90CA-48C1B878D97F} = {3B5A6BD8-2B94-42F9-B122-06D92C470B31} {252B974D-7725-44F5-BE69-330B85731B08} = {3B5A6BD8-2B94-42F9-B122-06D92C470B31} {1BC0B4EF-7C35-4BB7-B207-180FBC55681D} = {3B5A6BD8-2B94-42F9-B122-06D92C470B31} - {B8EE61E9-F029-49EA-81E5-D0BCBA265418} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {1BBE72D9-3509-4750-B79E-766B3122B999} = {35480478-646E-45F1-96A3-EE7AC498ACE3} - {94539590-4B6F-4500-B010-DED4D4A30529} = {1BBE72D9-3509-4750-B79E-766B3122B999} - {85C84F76-3730-4D17-923E-96B665544B57} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {F958B922-0418-4AB8-B5C6-86510DB52F38} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {70EB3FE2-9EB5-4B62-9405-71D1A332B0FD} = {346A18CA-EB1F-483B-8BC3-EDF250DA72E6} {2DF28873-A172-42EB-8FBD-324DF8AD9323} = {2AD59714-541D-4794-9216-6EAB25F271DA} {9B731750-D28D-4DE0-AC8E-280E932BD301} = {346A18CA-EB1F-483B-8BC3-EDF250DA72E6} @@ -600,12 +613,10 @@ Global {12AB0F40-141F-4C65-A630-7A4D4421EEEE} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {25B7376F-D770-4C4A-8EAB-73465BA962BA} = {12AB0F40-141F-4C65-A630-7A4D4421EEEE} {3585C2D1-9AA8-4B83-918D-588D701E37F5} = {12AB0F40-141F-4C65-A630-7A4D4421EEEE} - {89865D7E-8769-477B-8CED-C5B53C981178} = {12AB0F40-141F-4C65-A630-7A4D4421EEEE} {332A0B08-B1C9-4321-AD31-B2102B95AFEA} = {B7B4B68F-7086-49F2-B401-C76B4EF0B320} {0530F522-2556-4D69-8275-86947E9D51EF} = {8C52EB41-3815-4B12-9E40-C0D1472361CA} {1A6572FD-D274-4700-901F-6F9F7A49F57B} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {8C96F00C-DD52-4808-9AC2-1C937257B47B} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} - {D159BBC6-8D73-44AB-A17B-B14AB7D56A0D} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {8300ACD9-51DA-4B69-B7DF-FB2E5CC63F73} = {2AD59714-541D-4794-9216-6EAB25F271DA} {ECCC27F2-F7ED-406C-90B8-BD53A8A59B47} = {8300ACD9-51DA-4B69-B7DF-FB2E5CC63F73} {3D52F777-3A84-4238-974F-A5969085B472} = {8300ACD9-51DA-4B69-B7DF-FB2E5CC63F73} @@ -617,7 +628,6 @@ Global {195344A3-DE14-4283-B7FB-3A5822C5C0FC} = {DEC72475-9783-4BD9-B31E-F77758269476} {8A5ECE73-1529-4B53-8907-735ADA05C20A} = {8300ACD9-51DA-4B69-B7DF-FB2E5CC63F73} {CB97DC2A-EE66-43A6-977F-6CB82C030D7A} = {DEC72475-9783-4BD9-B31E-F77758269476} - {949C67AD-CF8F-4357-A3EF-D3DA407B525E} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} {F4F6BA53-C40D-4168-9EE0-37DD9FC42665} = {2AD59714-541D-4794-9216-6EAB25F271DA} {FEA7D21C-E494-4E9C-A2F5-6D1F79FC07A9} = {F4F6BA53-C40D-4168-9EE0-37DD9FC42665} {08C7BAAF-80FD-4AAE-8400-2A2D12D6CF06} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} @@ -628,6 +638,35 @@ Global {49938A37-DA38-47A0-ADF7-DF2221F7F4A7} = {2AD59714-541D-4794-9216-6EAB25F271DA} {9D1C8062-6388-4713-A81D-8B2F2C2D336D} = {49938A37-DA38-47A0-ADF7-DF2221F7F4A7} {D27A078E-1409-4B0B-AD7E-DD4528206049} = {08C7BAAF-80FD-4AAE-8400-2A2D12D6CF06} + {E9B52BA7-B0B3-4823-9BB3-8B4AA40CB2C8} = {DEC72475-9783-4BD9-B31E-F77758269476} + {DB9DA043-4E18-433B-BD71-2CCC8A5E28C0} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} + {D159BBC6-8D73-44AB-A17B-B14AB7D56A0D} = {DB9DA043-4E18-433B-BD71-2CCC8A5E28C0} + {EF3D6AA7-0334-4ECC-A1FD-C68856B7B954} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} + {79DEC257-916B-402D-BBB9-BF4E6518FF8F} = {EF3D6AA7-0334-4ECC-A1FD-C68856B7B954} + {1EE16F3E-B208-4211-9C77-9A24037668AC} = {EF3D6AA7-0334-4ECC-A1FD-C68856B7B954} + {34F34B4D-5BA2-45EC-8BC0-2507AF50A9A5} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} + {C010219B-6ECA-429F-98BA-839F6BAC0D6B} = {34F34B4D-5BA2-45EC-8BC0-2507AF50A9A5} + {949C67AD-CF8F-4357-A3EF-D3DA407B525E} = {34F34B4D-5BA2-45EC-8BC0-2507AF50A9A5} + {2CC72293-C3FB-4A01-922A-E8C7CCD689A0} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} + {FD291718-B989-4660-AE04-70F7A456B7BB} = {2CC72293-C3FB-4A01-922A-E8C7CCD689A0} + {85C84F76-3730-4D17-923E-96B665544B57} = {2CC72293-C3FB-4A01-922A-E8C7CCD689A0} + {F958B922-0418-4AB8-B5C6-86510DB52F38} = {2CC72293-C3FB-4A01-922A-E8C7CCD689A0} + {3A9F8D81-DB62-43D3-841C-9F37D8238E36} = {DB9DA043-4E18-433B-BD71-2CCC8A5E28C0} + {1A8FB2E2-4272-402F-9301-9C75FEA2E9C1} = {DB9DA043-4E18-433B-BD71-2CCC8A5E28C0} + {4E5352E6-9F06-4A92-A6D9-E82DBA516E72} = {DB9DA043-4E18-433B-BD71-2CCC8A5E28C0} + {0D3F6F1E-D5A5-4E6A-8140-4FE03601E084} = {346A18CA-EB1F-483B-8BC3-EDF250DA72E6} + {D9752FF9-AC66-4CC9-827E-65F16868BF0E} = {0D3F6F1E-D5A5-4E6A-8140-4FE03601E084} + {2FEF33B2-11B2-4D6A-B568-69FCDF6F098C} = {0D3F6F1E-D5A5-4E6A-8140-4FE03601E084} + {39412AE4-B325-4827-B24E-C15415DCA7E9} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} + {88039BCA-7501-48C8-9F94-9F8C2C0A19A5} = {39412AE4-B325-4827-B24E-C15415DCA7E9} + {9F898466-739B-4DE0-BFD7-6B5203957236} = {39412AE4-B325-4827-B24E-C15415DCA7E9} + {0F1EE8FF-4CE4-49D5-BF49-3837EB3BD637} = {39412AE4-B325-4827-B24E-C15415DCA7E9} + {9D2CCAB5-67AB-4A1A-B329-77F7973DB2A2} = {8DA4E10C-3EF4-42CD-BB51-A6562A7A0633} + {71008870-2212-4A74-8777-B5B268C27911} = {9D2CCAB5-67AB-4A1A-B329-77F7973DB2A2} + {4B590BAD-B92D-427C-B340-68A40A9C9B95} = {2AD59714-541D-4794-9216-6EAB25F271DA} + {2E48B176-D743-49B3-B1F2-6EC159EB80EF} = {4B590BAD-B92D-427C-B340-68A40A9C9B95} + {F0B6FF75-499F-463D-A39E-AB39BFAC0F8D} = {4B590BAD-B92D-427C-B340-68A40A9C9B95} + {B64F1EA1-9AEA-4828-BC8B-363FDFD8210F} = {39412AE4-B325-4827-B24E-C15415DCA7E9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2012E26A-91F8-40F9-9118-457668C6B7BA} diff --git a/Messaging.Sample.Telegram.Standalone/Messaging.Sample.Telegram.Standalone.csproj b/Messaging.Sample.Telegram.Standalone/Messaging.Sample.Telegram.Standalone.csproj new file mode 100644 index 00000000..9c561d64 --- /dev/null +++ b/Messaging.Sample.Telegram.Standalone/Messaging.Sample.Telegram.Standalone.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + enable + 0.8.0 + Botticelli + Igor Evdokimov + https://github.com/devgopher/botticelli + logo.jpg + https://github.com/devgopher/botticelli + TelegramMessagingSample.Standalone + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + Never + true + Never + + + + + + + + + + + \ No newline at end of file diff --git a/Messaging.Sample.Telegram.Standalone/Program.cs b/Messaging.Sample.Telegram.Standalone/Program.cs new file mode 100644 index 00000000..66f5ec1a --- /dev/null +++ b/Messaging.Sample.Telegram.Standalone/Program.cs @@ -0,0 +1,33 @@ +using Botticelli.Framework.Commands.Validators; +using Botticelli.Framework.Extensions; +using Botticelli.Framework.Telegram.Extensions; +using Botticelli.Schedule.Quartz.Extensions; +using MessagingSample.Common.Commands; +using MessagingSample.Common.Commands.Processors; +using NLog.Extensions.Logging; +using Telegram.Bot.Types.ReplyMarkups; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services + .AddStandaloneTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services + .AddTelegramLayoutsSupport() + .AddLogging(cfg => cfg.AddNLog()) + .AddQuartzScheduler(builder.Configuration); + +builder.Services.AddBotCommand() + .AddProcessor>() + .AddValidator>(); + +builder.Services.AddBotCommand() + .AddProcessor>() + .AddValidator>(); + +builder.Services.AddBotCommand() + .AddProcessor>() + .AddValidator>(); + +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Samples/Ai.Messaging.Sample.Vk/Properties/launchSettings.json b/Messaging.Sample.Telegram.Standalone/Properties/launchSettings.json similarity index 77% rename from Samples/Ai.Messaging.Sample.Vk/Properties/launchSettings.json rename to Messaging.Sample.Telegram.Standalone/Properties/launchSettings.json index d66674ad..59fc630c 100644 --- a/Samples/Ai.Messaging.Sample.Vk/Properties/launchSettings.json +++ b/Messaging.Sample.Telegram.Standalone/Properties/launchSettings.json @@ -1,12 +1,12 @@ { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { - "TelegramBotSample": { + "TelegramBotSample.Standalone": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, "launchUrl": "", - "applicationUrl": "http://localhost:5073", + "applicationUrl": "http://localhost:5076", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Messaging.Sample.Telegram.Standalone/appsettings.json b/Messaging.Sample.Telegram.Standalone/appsettings.json new file mode 100644 index 00000000..8d03590a --- /dev/null +++ b/Messaging.Sample.Telegram.Standalone/appsettings.json @@ -0,0 +1,22 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "DataAccess": { + "ConnectionString": "database.db;Password=123" + }, + "TelegramBot": { + "timeout": 60, + "useThrottling": false, + "useTestEnvironment": false, + "name": "TestBot" + }, + "BotData": { + "BotId": "botId", + "BotKey": "5746549361:AAFZcvuRcEk7QO4OfAjTYQQUeUpcaES3kqk" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/Pay.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs b/Pay.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs index 52b28a68..6c4e99bd 100644 --- a/Pay.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs +++ b/Pay.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs @@ -7,19 +7,17 @@ namespace TelegramPayBot.Commands.Processors; -public class InfoCommandProcessor : CommandProcessor where TReplyMarkup : class +public class InfoCommandProcessor( + ILogger> logger, + ICommandValidator commandValidator, + MetricsProcessor metricsProcessor, + IValidator messageValidator) + : CommandProcessor(logger, + commandValidator, + messageValidator, + metricsProcessor) + where TReplyMarkup : class { - public InfoCommandProcessor(ILogger> logger, - ICommandValidator commandValidator, - MetricsProcessor metricsProcessor, - IValidator messageValidator) - : base(logger, - commandValidator, - metricsProcessor, - messageValidator) - { - } - protected override Task InnerProcessContact(Message message, CancellationToken token) { return Task.CompletedTask; @@ -47,6 +45,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot.SendMessageAsync(greetingMessageRequest, token); + await SendMessage(greetingMessageRequest, token); } } \ No newline at end of file diff --git a/Pay.Sample.Telegram/Commands/Processors/SendInvoiceCommandProcessor.cs b/Pay.Sample.Telegram/Commands/Processors/SendInvoiceCommandProcessor.cs index d3736d6c..87137ab5 100644 --- a/Pay.Sample.Telegram/Commands/Processors/SendInvoiceCommandProcessor.cs +++ b/Pay.Sample.Telegram/Commands/Processors/SendInvoiceCommandProcessor.cs @@ -23,8 +23,8 @@ public SendInvoiceCommandProcessor(ILogger paySettingsAccessor) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { _paySettingsAccessor = paySettingsAccessor; } @@ -77,6 +77,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot?.SendMessageAsync(sendInvoiceMessageRequest, token)!; // TODO: think about Bot mocks + await SendMessage(sendInvoiceMessageRequest, token); } } \ No newline at end of file diff --git a/Pay.Sample.Telegram/Program.cs b/Pay.Sample.Telegram/Program.cs index 0e9fd281..fdbcb85a 100644 --- a/Pay.Sample.Telegram/Program.cs +++ b/Pay.Sample.Telegram/Program.cs @@ -1,6 +1,7 @@ +using Botticelli.Controls.Parsers; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Framework.Extensions; +using Botticelli.Interfaces; using Botticelli.Pay.Models; using Botticelli.Pay.Processors; using Botticelli.Pay.Telegram.Extensions; @@ -16,7 +17,10 @@ builder.Services .Configure(builder.Configuration.GetSection("PaySettings")) .AddTelegramPayBot>(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) + .Prepare(); + + +builder.Services.AddLogging(cfg => cfg.AddNLog()) .AddSingleton(); builder.Services.AddBotCommand() @@ -29,4 +33,4 @@ var app = builder.Build(); -app.Run(); \ No newline at end of file +await app.RunAsync(); \ No newline at end of file diff --git a/Pay.Sample.Telegram/Settings/PaySettings.cs b/Pay.Sample.Telegram/Settings/PaySettings.cs index 3ea3b55c..8e1eb845 100644 --- a/Pay.Sample.Telegram/Settings/PaySettings.cs +++ b/Pay.Sample.Telegram/Settings/PaySettings.cs @@ -5,5 +5,5 @@ namespace TelegramPayBot.Settings; public class PaySettings { [JsonPropertyName("ProviderToken")] - public string? ProviderToken { get; set; } + public string? ProviderToken { get; init; } } \ No newline at end of file diff --git a/Samples/Ai.ChatGpt.Sample.Vk/Ai.ChatGpt.Sample.Vk.csproj b/Samples/Ai.ChatGpt.Sample.Vk/Ai.ChatGpt.Sample.Vk.csproj deleted file mode 100644 index 31501d74..00000000 --- a/Samples/Ai.ChatGpt.Sample.Vk/Ai.ChatGpt.Sample.Vk.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - net8.0 - enable - enable - 0.7.0 - Botticelli - Igor Evdokimov - https://github.com/devgopher/botticelli - logo.jpg - https://github.com/devgopher/botticelli - VkAiChatGptSample - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - - nlog.config - PreserveNewest - - - - \ No newline at end of file diff --git a/Samples/Ai.ChatGpt.Sample.Vk/Program.cs b/Samples/Ai.ChatGpt.Sample.Vk/Program.cs deleted file mode 100644 index 3e3c345f..00000000 --- a/Samples/Ai.ChatGpt.Sample.Vk/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -using AiSample.Common; -using AiSample.Common.Commands; -using AiSample.Common.Handlers; -using AiSample.Common.Settings; -using Botticelli.AI.ChatGpt.Extensions; -using Botticelli.Bus.None.Extensions; -using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Extensions; -using Botticelli.Framework.Vk.Messages; -using Botticelli.Framework.Vk.Messages.API.Markups; -using Botticelli.Framework.Vk.Messages.Extensions; -using Botticelli.Interfaces; -using NLog.Extensions.Logging; - -var builder = WebApplication.CreateBuilder(args); - -var settings = builder.Configuration - .GetSection(nameof(SampleSettings)) - .Get(); - -builder.Services.AddVkBot(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) - .AddChatGptProvider(builder.Configuration) - .AddScoped, PassValidator>() - .AddSingleton() - .UsePassBusAgent, AiHandler>() - .UsePassBusClient>() - .AddBotCommand, PassValidator>(); - -var app = builder.Build(); - -app.Run(); \ No newline at end of file diff --git a/Samples/Ai.ChatGpt.Sample.Vk/Properties/launchSettings.json b/Samples/Ai.ChatGpt.Sample.Vk/Properties/launchSettings.json deleted file mode 100644 index cd6d2064..00000000 --- a/Samples/Ai.ChatGpt.Sample.Vk/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "VkAiChatGptSample": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:54851;http://localhost:54852" - } - } -} \ No newline at end of file diff --git a/Samples/Ai.Common.Sample/Ai.Common.Sample.csproj b/Samples/Ai.Common.Sample/Ai.Common.Sample.csproj index 84aa9c9a..09fb90cf 100644 --- a/Samples/Ai.Common.Sample/Ai.Common.Sample.csproj +++ b/Samples/Ai.Common.Sample/Ai.Common.Sample.csproj @@ -8,12 +8,12 @@ - + - + diff --git a/Samples/Ai.Common.Sample/AiCommandProcessor.cs b/Samples/Ai.Common.Sample/AiCommandProcessor.cs index 00753e7a..d689959a 100644 --- a/Samples/Ai.Common.Sample/AiCommandProcessor.cs +++ b/Samples/Ai.Common.Sample/AiCommandProcessor.cs @@ -6,7 +6,7 @@ using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Utils; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Parsers; using Botticelli.Framework.SendOptions; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; @@ -27,8 +27,8 @@ public AiCommandProcessor(ILogger> logger, IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { _bus = bus; var responseLayout = new AiLayout(); @@ -38,15 +38,15 @@ public AiCommandProcessor(ILogger> logger, _bus.OnReceived += async (sender, response) => { - await Bot.SendMessageAsync(new SendMessageRequest(response.Uid) - { - Message = response.Message, - ExpectPartialResponse = response.IsPartial, - SequenceNumber = response.SequenceNumber, - IsFinal = response.IsFinal - }, - options, - CancellationToken.None); + await SendMessage(new SendMessageRequest(response.Uid) + { + Message = response.Message, + ExpectPartialResponse = response.IsPartial, + SequenceNumber = response.SequenceNumber, + IsFinal = response.IsFinal + }, + options, + CancellationToken.None); }; } diff --git a/Samples/Ai.Common.Sample/Handlers/AiHandler.cs b/Samples/Ai.Common.Sample/Handlers/AiHandler.cs index 135ae779..f51366f5 100644 --- a/Samples/Ai.Common.Sample/Handlers/AiHandler.cs +++ b/Samples/Ai.Common.Sample/Handlers/AiHandler.cs @@ -35,7 +35,7 @@ await _provider.SendAsync(new AiMessage(input.Uid) } catch (Exception ex) { - _logger.LogError($"Error while handling a message from AI backend: {ex.Message}", ex); + _logger.LogError("Error while handling a message from AI backend: {ExMessage}", ex.Message, ex); } } } \ No newline at end of file diff --git a/Samples/Ai.Common.Sample/Layouts/AiLayout.cs b/Samples/Ai.Common.Sample/Layouts/AiLayout.cs index f0e7397d..654f50bc 100644 --- a/Samples/Ai.Common.Sample/Layouts/AiLayout.cs +++ b/Samples/Ai.Common.Sample/Layouts/AiLayout.cs @@ -1,5 +1,5 @@ -using Botticelli.Framework.Controls.BasicControls; -using Botticelli.Framework.Controls.Layouts; +using Botticelli.Controls.BasicControls; +using Botticelli.Controls.Layouts; namespace AiSample.Common.Layouts; diff --git a/Samples/Ai.DeepSeek.Sample.Telegram/Program.cs b/Samples/Ai.DeepSeek.Sample.Telegram/Program.cs index d634cd12..395b6218 100644 --- a/Samples/Ai.DeepSeek.Sample.Telegram/Program.cs +++ b/Samples/Ai.DeepSeek.Sample.Telegram/Program.cs @@ -15,18 +15,21 @@ var builder = WebApplication.CreateBuilder(args); builder.Services - .AddTelegramBot(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) - .AddDeepSeekProvider(builder.Configuration) - .AddAiValidation() - .AddScoped, PassValidator>() - .AddSingleton() - .UsePassBusAgent, AiHandler>() - .UsePassBusClient>() - .UsePassEventBusClient>() - .AddBotCommand, PassValidator>() - .AddTelegramLayoutsSupport(); + .AddTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services + .AddLogging(cfg => cfg.AddNLog()) + .AddDeepSeekProvider(builder.Configuration) + .AddAiValidation() + .AddScoped, PassValidator>() + .AddSingleton() + .UsePassBusAgent, AiHandler>() + .UsePassBusClient>() + .UsePassEventBusClient>() + .AddBotCommand, PassValidator>() + .AddTelegramLayoutsSupport(); var app = builder.Build(); -app.Run(); \ No newline at end of file +await app.RunAsync(); \ No newline at end of file diff --git a/Samples/Ai.Messaging.Sample.Vk/Ai.Messaging.Sample.Vk.csproj b/Samples/Ai.Messaging.Sample.Vk/Ai.Messaging.Sample.Vk.csproj deleted file mode 100644 index 8746e7e8..00000000 --- a/Samples/Ai.Messaging.Sample.Vk/Ai.Messaging.Sample.Vk.csproj +++ /dev/null @@ -1,46 +0,0 @@ - - - - net8.0 - enable - enable - 0.7.0 - Botticelli - Igor Evdokimov - https://github.com/devgopher/botticelli - logo.jpg - https://github.com/devgopher/botticelli - VkMessagingSample - - - - - - - - - - - - - - - - - - - Never - true - Never - - - nlog.config - PreserveNewest - - - - - - - - \ No newline at end of file diff --git a/Samples/Ai.Messaging.Sample.Vk/Program.cs b/Samples/Ai.Messaging.Sample.Vk/Program.cs deleted file mode 100644 index 7466e643..00000000 --- a/Samples/Ai.Messaging.Sample.Vk/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Extensions; -using Botticelli.Framework.Vk.Messages.API.Markups; -using Botticelli.Framework.Vk.Messages.Extensions; -using Botticelli.Schedule.Quartz.Extensions; -using Botticelli.Talks.Extensions; -using MessagingSample.Common.Commands; -using MessagingSample.Common.Commands.Processors; -using NLog.Extensions.Logging; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services - .AddVkBot(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) - .AddQuartzScheduler(builder.Configuration) - .AddScoped>() - .AddScoped>() - .AddOpenTtsTalks(builder.Configuration) - .AddBotCommand, PassValidator>() - .AddBotCommand, PassValidator>(); - - -var app = builder.Build(); - -app.Run(); \ No newline at end of file diff --git a/Samples/Ai.Messaging.Sample.Vk/deploy/run_standalone.sh b/Samples/Ai.Messaging.Sample.Vk/deploy/run_standalone.sh deleted file mode 100644 index e50d9012..00000000 --- a/Samples/Ai.Messaging.Sample.Vk/deploy/run_standalone.sh +++ /dev/null @@ -1,18 +0,0 @@ -sudo apt-get update -sudo apt-get install -y dotnet-sdk-7.0 dotnet-runtime-7.0 aspnetcore-runtime-7.0 - - -rm -rf botticelli/ -git clone https://github.com/devgopher/botticelli.git -pushd botticelli/ -git checkout release/0.3 -git pull - -pushd VkMessagingSample - -dotnet run VkMessagingSample.csproj & - -echo BOT ID: -cat Data/botId - -popd \ No newline at end of file diff --git a/Samples/Ai.YaGpt.Sample.Telegram/Program.cs b/Samples/Ai.YaGpt.Sample.Telegram/Program.cs index ccb6bada..3b748839 100644 --- a/Samples/Ai.YaGpt.Sample.Telegram/Program.cs +++ b/Samples/Ai.YaGpt.Sample.Telegram/Program.cs @@ -15,7 +15,10 @@ var builder = WebApplication.CreateBuilder(args); builder.Services - .AddTelegramBot(builder.Configuration) + .AddTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services .AddLogging(cfg => cfg.AddNLog()) .AddYaGptProvider(builder.Configuration) .AddAiValidation() @@ -28,4 +31,4 @@ var app = builder.Build(); -app.Run(); \ No newline at end of file +await app.RunAsync(); \ No newline at end of file diff --git a/Samples/Ai.YaGpt.Sample.Vk/Ai.YaGpt.Sample.Vk.csproj b/Samples/Ai.YaGpt.Sample.Vk/Ai.YaGpt.Sample.Vk.csproj deleted file mode 100644 index f3af7548..00000000 --- a/Samples/Ai.YaGpt.Sample.Vk/Ai.YaGpt.Sample.Vk.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net8.0 - enable - enable - 0.7.0 - Botticelli - Igor Evdokimov - https://github.com/devgopher/botticelli - logo.jpg - https://github.com/devgopher/botticelli - VkAiChatGptSample - - - - - nlog.config - PreserveNewest - - - - - - - - - - - diff --git a/Samples/Ai.YaGpt.Sample.Vk/Program.cs b/Samples/Ai.YaGpt.Sample.Vk/Program.cs deleted file mode 100644 index 2a32783c..00000000 --- a/Samples/Ai.YaGpt.Sample.Vk/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -using AiSample.Common; -using AiSample.Common.Commands; -using AiSample.Common.Handlers; -using AiSample.Common.Settings; -using Botticelli.AI.YaGpt.Extensions; -using Botticelli.Bus.None.Extensions; -using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Extensions; -using Botticelli.Framework.Vk.Messages; -using Botticelli.Framework.Vk.Messages.API.Markups; -using Botticelli.Framework.Vk.Messages.Extensions; -using Botticelli.Interfaces; -using NLog.Extensions.Logging; - -var builder = WebApplication.CreateBuilder(args); - -var settings = builder.Configuration - .GetSection(nameof(SampleSettings)) - .Get(); - -builder.Services.AddVkBot(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) - .AddYaGptProvider(builder.Configuration) - .AddScoped, PassValidator>() - .AddSingleton() - .UsePassBusAgent, AiHandler>() - .UsePassBusClient>() - .AddBotCommand, PassValidator>(); - -var app = builder.Build(); - -app.Run(); \ No newline at end of file diff --git a/Samples/Ai.YaGpt.Sample.Vk/Properties/launchSettings.json b/Samples/Ai.YaGpt.Sample.Vk/Properties/launchSettings.json deleted file mode 100644 index f102d779..00000000 --- a/Samples/Ai.YaGpt.Sample.Vk/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "VkAiYaGptSample": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:54851;http://localhost:54852" - } - } -} \ No newline at end of file diff --git a/Samples/Auth.Sample.Telegram/Auth.Sample.Telegram.csproj b/Samples/Auth.Sample.Telegram/Auth.Sample.Telegram.csproj index 2f8e7bb2..ab014cde 100644 --- a/Samples/Auth.Sample.Telegram/Auth.Sample.Telegram.csproj +++ b/Samples/Auth.Sample.Telegram/Auth.Sample.Telegram.csproj @@ -7,8 +7,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Samples/Auth.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs b/Samples/Auth.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs index a21deceb..7781b1c9 100644 --- a/Samples/Auth.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs +++ b/Samples/Auth.Sample.Telegram/Commands/Processors/InfoCommandProcessor.cs @@ -1,7 +1,6 @@ using Botticelli.Client.Analytics; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; using FluentValidation; @@ -13,13 +12,11 @@ public class InfoCommandProcessor : CommandProcessor public InfoCommandProcessor(ILogger> logger, ICommandValidator commandValidator, MetricsProcessor metricsProcessor, - ILayoutSupplier layoutSupplier, - ILayoutParser layoutParser, IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { } @@ -50,6 +47,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot?.SendMessageAsync(greetingMessageRequest, token)!; + await SendMessage(greetingMessageRequest, token); } } \ No newline at end of file diff --git a/Samples/Auth.Sample.Telegram/Commands/Processors/RegisterCommandProcessor.cs b/Samples/Auth.Sample.Telegram/Commands/Processors/RegisterCommandProcessor.cs index fc79dad2..5051c622 100644 --- a/Samples/Auth.Sample.Telegram/Commands/Processors/RegisterCommandProcessor.cs +++ b/Samples/Auth.Sample.Telegram/Commands/Processors/RegisterCommandProcessor.cs @@ -29,8 +29,8 @@ public class RegisterCommandProcessor( IValidator messageValidator) : CommandProcessor(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) where TReplyMarkup : class { protected override Task InnerProcessContact(Message message, CancellationToken token) @@ -69,7 +69,7 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot.SendMessageAsync(alreadyRegisteredRequest, token); + await SendMessage(alreadyRegisteredRequest, token); return; } @@ -103,7 +103,7 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot.SendMessageAsync(registeredRequest, token)!; + await SendMessage(registeredRequest, token); } private async Task GetUserRole() diff --git a/Samples/Auth.Sample.Telegram/Commands/Processors/StartCommandProcessor.cs b/Samples/Auth.Sample.Telegram/Commands/Processors/StartCommandProcessor.cs index 93bb3609..24947240 100644 --- a/Samples/Auth.Sample.Telegram/Commands/Processors/StartCommandProcessor.cs +++ b/Samples/Auth.Sample.Telegram/Commands/Processors/StartCommandProcessor.cs @@ -4,9 +4,9 @@ using Botticelli.Auth.Dto.User; using Botticelli.Auth.Services; using Botticelli.Client.Analytics; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Framework.SendOptions; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; @@ -30,8 +30,8 @@ public StartCommandProcessor(ILogger> logger IManager roleManager) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { _userInfo = userInfo; _roleManager = roleManager; @@ -72,6 +72,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot.SendMessageAsync(greetingMessageRequest, _options, token); + await SendMessage(greetingMessageRequest, _options, token); } } \ No newline at end of file diff --git a/Samples/Auth.Sample.Telegram/Program.cs b/Samples/Auth.Sample.Telegram/Program.cs index 894b4404..7014355c 100644 --- a/Samples/Auth.Sample.Telegram/Program.cs +++ b/Samples/Auth.Sample.Telegram/Program.cs @@ -11,6 +11,9 @@ builder.Services .AddTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services .AddTelegramLayoutsSupport() .AddLogging(cfg => cfg.AddNLog()) .AddSqliteBasicBotUserAuth(builder.Configuration); @@ -27,4 +30,4 @@ .AddProcessor>() .AddValidator>(); -builder.Build().Run(); \ No newline at end of file +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Samples/Auth.Sample.Telegram/Properties/launchSettings.json b/Samples/Auth.Sample.Telegram/Properties/launchSettings.json index 787507ea..81282668 100644 --- a/Samples/Auth.Sample.Telegram/Properties/launchSettings.json +++ b/Samples/Auth.Sample.Telegram/Properties/launchSettings.json @@ -1,41 +1,15 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:25409", - "sslPort": 44388 - } - }, + "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { - "http": { + "TelegramBotSample": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5128", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7171;http://localhost:5128", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", + "launchBrowser": false, + "launchUrl": "", + "applicationUrl": "http://localhost:5045", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} +} \ No newline at end of file diff --git a/Samples/Broadcasting.Sample.Common/Broadcasting.Sample.Common.csproj b/Samples/Broadcasting.Sample.Common/Broadcasting.Sample.Common.csproj new file mode 100644 index 00000000..5cd98459 --- /dev/null +++ b/Samples/Broadcasting.Sample.Common/Broadcasting.Sample.Common.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + 0.8.0 + + + + + + + diff --git a/Samples/Broadcasting.Sample.Common/Commands/Processors/StartCommandProcessor.cs b/Samples/Broadcasting.Sample.Common/Commands/Processors/StartCommandProcessor.cs new file mode 100644 index 00000000..d093cc90 --- /dev/null +++ b/Samples/Broadcasting.Sample.Common/Commands/Processors/StartCommandProcessor.cs @@ -0,0 +1,50 @@ +using Botticelli.Framework.Commands.Processors; +using Botticelli.Framework.Commands.Validators; +using Botticelli.Shared.API.Client.Requests; +using Botticelli.Shared.ValueObjects; +using FluentValidation; +using Microsoft.Extensions.Logging; + +namespace Broadcasting.Sample.Common.Commands.Processors; + +public class StartCommandProcessor : CommandProcessor where TReplyMarkup : class +{ + public StartCommandProcessor(ILogger> logger, + ICommandValidator commandValidator, + IValidator messageValidator) + : base(logger, + commandValidator, + messageValidator) + { + } + + + protected override Task InnerProcessContact(Message message, CancellationToken token) + { + return Task.CompletedTask; + } + + protected override Task InnerProcessPoll(Message message, CancellationToken token) + { + return Task.CompletedTask; + } + + protected override Task InnerProcessLocation(Message message, CancellationToken token) + { + return Task.CompletedTask; + } + + protected override async Task InnerProcess(Message message, CancellationToken token) + { + var greetingMessageRequest = new SendMessageRequest + { + Message = new Message + { + ChatIds = message.ChatIds, + Body = "Bot started. Please, wait for broadcast messages from admin..." + } + }; + + await SendMessage(greetingMessageRequest, token); + } +} \ No newline at end of file diff --git a/Samples/Broadcasting.Sample.Common/Commands/StartCommand.cs b/Samples/Broadcasting.Sample.Common/Commands/StartCommand.cs new file mode 100644 index 00000000..dc6bc814 --- /dev/null +++ b/Samples/Broadcasting.Sample.Common/Commands/StartCommand.cs @@ -0,0 +1,8 @@ +using Botticelli.Framework.Commands; + +namespace Broadcasting.Sample.Common.Commands; + +public class StartCommand : ICommand +{ + public Guid Id { get; } +} \ No newline at end of file diff --git a/Samples/Broadcasting.Sample.Telegram/Broadcasting.Sample.Telegram.csproj b/Samples/Broadcasting.Sample.Telegram/Broadcasting.Sample.Telegram.csproj new file mode 100644 index 00000000..3585068c --- /dev/null +++ b/Samples/Broadcasting.Sample.Telegram/Broadcasting.Sample.Telegram.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Samples/Broadcasting.Sample.Telegram/Program.cs b/Samples/Broadcasting.Sample.Telegram/Program.cs new file mode 100644 index 00000000..65e8c867 --- /dev/null +++ b/Samples/Broadcasting.Sample.Telegram/Program.cs @@ -0,0 +1,24 @@ +using Botticelli.Broadcasting.Telegram.Extensions; +using Botticelli.Framework.Commands.Validators; +using Botticelli.Framework.Extensions; +using Botticelli.Framework.Telegram.Extensions; +using Broadcasting.Sample.Common.Commands; +using Broadcasting.Sample.Common.Commands.Processors; +using Telegram.Bot.Types.ReplyMarkups; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services + .AddTelegramBot(builder.Configuration) + .AddTelegramBroadcasting(builder.Configuration) + .Prepare(); + +builder.Services.AddBotCommand() + .AddProcessor>() + .AddValidator>(); + +var app = builder.Build(); +app.Urls.Clear(); +app.Urls.Add("http://localhost:5044"); + +await app.RunAsync(); \ No newline at end of file diff --git a/Samples/Broadcasting.Sample.Telegram/appsettings.json b/Samples/Broadcasting.Sample.Telegram/appsettings.json new file mode 100644 index 00000000..01bef1ab --- /dev/null +++ b/Samples/Broadcasting.Sample.Telegram/appsettings.json @@ -0,0 +1,30 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "DataAccess": { + "ConnectionString": "database.db;Password=123" + }, + "Server": { + "ServerUri": "https://localhost:7247/v1/" + }, + "AnalyticsClient": { + "TargetUrl": "http://localhost:5251/v1/" + }, + "TelegramBot": { + "timeout": 60, + "useThrottling": false, + "useTestEnvironment": false, + "name": "TestBot" + }, + "Broadcasting": { + "ConnectionString": "broadcasting_database.db;Password=321", + "BotId": "chatbot1", + "HowOld": "00:10:00", + "ServerUri": "https://localhost:7247/v1" + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/Samples/CommandChain.Sample.Telegram/CommandChain.Sample.Telegram.csproj b/Samples/CommandChain.Sample.Telegram/CommandChain.Sample.Telegram.csproj index 8fbb15d6..07a28fa1 100644 --- a/Samples/CommandChain.Sample.Telegram/CommandChain.Sample.Telegram.csproj +++ b/Samples/CommandChain.Sample.Telegram/CommandChain.Sample.Telegram.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli diff --git a/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/GetNameCommandProcessor.cs b/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/GetNameCommandProcessor.cs index 6c144221..aa477b68 100644 --- a/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/GetNameCommandProcessor.cs +++ b/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/GetNameCommandProcessor.cs @@ -1,7 +1,6 @@ using Botticelli.Client.Analytics; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; using FluentValidation; @@ -29,10 +28,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to Body = "Hello! What's your name?" }; - await Bot.SendMessageAsync(new SendMessageRequest - { - Message = responseMessage - }, - token); + await SendMessage(responseMessage, token); } } \ No newline at end of file diff --git a/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/SayHelloFinalCommandProcessor.cs b/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/SayHelloFinalCommandProcessor.cs index 93895818..9021ab29 100644 --- a/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/SayHelloFinalCommandProcessor.cs +++ b/Samples/CommandChain.Sample.Telegram/Commands/CommandProcessors/SayHelloFinalCommandProcessor.cs @@ -1,7 +1,6 @@ using Botticelli.Client.Analytics; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; using FluentValidation; @@ -22,11 +21,7 @@ public SayHelloFinalCommandProcessor(ILogger())}!"; - await Bot.SendMessageAsync(new SendMessageRequest - { - Message = message - }, - token); + await SendMessage(message, token); } protected override Task InnerProcess(Message message, CancellationToken token) diff --git a/Samples/CommandChain.Sample.Telegram/Program.cs b/Samples/CommandChain.Sample.Telegram/Program.cs index 32f6d488..b6a973f3 100644 --- a/Samples/CommandChain.Sample.Telegram/Program.cs +++ b/Samples/CommandChain.Sample.Telegram/Program.cs @@ -1,5 +1,5 @@ -using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Parsers; +using Botticelli.Framework.Commands.Validators; using Botticelli.Framework.Extensions; using Botticelli.Framework.Telegram; using Botticelli.Framework.Telegram.Extensions; @@ -13,8 +13,10 @@ var builder = WebApplication.CreateBuilder(args); builder.Services - .AddTelegramBot(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) + .AddTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services.AddLogging(cfg => cfg.AddNLog()) .AddSingleton>() .AddSingleton>() .AddSingleton>() @@ -33,4 +35,4 @@ app.Services.RegisterBotChainedCommand(); -app.Run(); \ No newline at end of file +await app.RunAsync(); \ No newline at end of file diff --git a/Samples/Layouts.Sample.Telegram/Handlers/DateChosenCommandProcessor.cs b/Samples/Layouts.Sample.Telegram/Handlers/DateChosenCommandProcessor.cs index 769766c7..8abdd2db 100644 --- a/Samples/Layouts.Sample.Telegram/Handlers/DateChosenCommandProcessor.cs +++ b/Samples/Layouts.Sample.Telegram/Handlers/DateChosenCommandProcessor.cs @@ -2,7 +2,7 @@ using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Utils; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Layouts.Commands.InlineCalendar; +using Botticelli.Controls.Layouts.Commands.InlineCalendar; using Botticelli.Interfaces; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; @@ -18,8 +18,8 @@ public class DateChosenCommandProcessor( IValidator messageValidator) : CommandProcessor(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { protected override async Task InnerProcess(Message message, CancellationToken token) { diff --git a/Samples/Layouts.Sample.Telegram/Handlers/GetCalendarCommandProcessor.cs b/Samples/Layouts.Sample.Telegram/Handlers/GetCalendarCommandProcessor.cs index c9e71657..ea7682db 100644 --- a/Samples/Layouts.Sample.Telegram/Handlers/GetCalendarCommandProcessor.cs +++ b/Samples/Layouts.Sample.Telegram/Handlers/GetCalendarCommandProcessor.cs @@ -2,8 +2,8 @@ using Botticelli.Client.Analytics; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Layouts.Inlines; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Layouts.Inlines; +using Botticelli.Controls.Parsers; using Botticelli.Framework.SendOptions; using Botticelli.Interfaces; using Botticelli.Shared.API.Client.Requests; @@ -27,8 +27,8 @@ public GetCalendarCommandProcessor(IBot bot, IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, - messageValidator) + messageValidator, + metricsProcessor) { _bot = bot; diff --git a/Samples/Layouts.Sample.Telegram/Layouts.Sample.Telegram.csproj b/Samples/Layouts.Sample.Telegram/Layouts.Sample.Telegram.csproj index c80a66db..dce5cd35 100644 --- a/Samples/Layouts.Sample.Telegram/Layouts.Sample.Telegram.csproj +++ b/Samples/Layouts.Sample.Telegram/Layouts.Sample.Telegram.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli @@ -20,7 +20,7 @@ - + diff --git a/Samples/Layouts.Sample.Telegram/Program.cs b/Samples/Layouts.Sample.Telegram/Program.cs index ba096edc..2704316a 100644 --- a/Samples/Layouts.Sample.Telegram/Program.cs +++ b/Samples/Layouts.Sample.Telegram/Program.cs @@ -1,5 +1,5 @@ using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Layouts.Extensions; +using Botticelli.Controls.Layouts.Extensions; using Botticelli.Framework.Extensions; using Botticelli.Framework.Telegram.Extensions; using Botticelli.Framework.Telegram.Layout; @@ -13,11 +13,12 @@ builder.Services .AddTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services .AddLogging(cfg => cfg.AddNLog()) .AddBotCommand>() .AddInlineCalendar() .AddOsmLocations(builder.Configuration); -var app = builder.Build(); - -app.Run(); \ No newline at end of file +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Samples/Layouts.Sample.Telegram/appsettings.json b/Samples/Layouts.Sample.Telegram/appsettings.json index 591ccfaa..db80d85d 100644 --- a/Samples/Layouts.Sample.Telegram/appsettings.json +++ b/Samples/Layouts.Sample.Telegram/appsettings.json @@ -6,7 +6,7 @@ } }, "DataAccess": { - "ConnectionString": "Filename=database.db;Password=123;ReadOnly=false" + "ConnectionString": "Filename=database.db;Password=123" }, "Server": { "ServerUri": "http://113.30.189.83:5042/v1/" diff --git a/Samples/Messaging.Sample.Common/Commands/Processors/InfoCommandProcessor.cs b/Samples/Messaging.Sample.Common/Commands/Processors/InfoCommandProcessor.cs index a0358201..53200a28 100644 --- a/Samples/Messaging.Sample.Common/Commands/Processors/InfoCommandProcessor.cs +++ b/Samples/Messaging.Sample.Common/Commands/Processors/InfoCommandProcessor.cs @@ -1,8 +1,8 @@ using System.Reflection; using Botticelli.Client.Analytics; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Framework.SendOptions; using Botticelli.Shared.API.Client.Requests; using Botticelli.Shared.ValueObjects; @@ -17,13 +17,11 @@ public class InfoCommandProcessor : CommandProcessor public InfoCommandProcessor(ILogger> logger, ICommandValidator commandValidator, - MetricsProcessor metricsProcessor, ILayoutSupplier layoutSupplier, ILayoutParser layoutParser, IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, messageValidator) { var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; @@ -33,6 +31,24 @@ public InfoCommandProcessor(ILogger> logger, _options = SendOptionsBuilder.CreateBuilder(responseMarkup); } + public InfoCommandProcessor(ILogger> logger, + ICommandValidator commandValidator, + ILayoutSupplier layoutSupplier, + ILayoutParser layoutParser, + IValidator messageValidator, + MetricsProcessor? metricsProcessor) + : base(logger, + commandValidator, + messageValidator, + metricsProcessor) + { + var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; + var responseLayout = layoutParser.ParseFromFile(Path.Combine(location, "main_layout.json")); + var responseMarkup = layoutSupplier.GetMarkup(responseLayout); + + _options = SendOptionsBuilder.CreateBuilder(responseMarkup); + } + protected override Task InnerProcessContact(Message message, CancellationToken token) { return Task.CompletedTask; @@ -60,6 +76,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot?.SendMessageAsync(greetingMessageRequest, _options, token)!; // TODO: think about Bot mocks + await SendMessage(greetingMessageRequest, _options, token); } } \ No newline at end of file diff --git a/Samples/Messaging.Sample.Common/Commands/Processors/StartCommandProcessor.cs b/Samples/Messaging.Sample.Common/Commands/Processors/StartCommandProcessor.cs index 5a9ebcda..02e6febb 100644 --- a/Samples/Messaging.Sample.Common/Commands/Processors/StartCommandProcessor.cs +++ b/Samples/Messaging.Sample.Common/Commands/Processors/StartCommandProcessor.cs @@ -1,9 +1,10 @@ using System.Reflection; using Botticelli.Client.Analytics; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Framework.SendOptions; +using Botticelli.Interfaces; using Botticelli.Scheduler; using Botticelli.Scheduler.Interfaces; using Botticelli.Shared.API.Client.Requests; @@ -18,26 +19,57 @@ public class StartCommandProcessor : CommandProcessor? _options; + private IBot? _bot; public StartCommandProcessor(ILogger> logger, ICommandValidator commandValidator, - MetricsProcessor metricsProcessor, IJobManager jobManager, ILayoutSupplier layoutSupplier, ILayoutParser layoutParser, IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, messageValidator) { _jobManager = jobManager; + var responseMarkup = Init(layoutSupplier, layoutParser); + + _options = SendOptionsBuilder.CreateBuilder(responseMarkup); + } + + public StartCommandProcessor(ILogger> logger, + ICommandValidator commandValidator, + IJobManager jobManager, + ILayoutSupplier layoutSupplier, + ILayoutParser layoutParser, + IValidator messageValidator, + MetricsProcessor? metricsProcessor) + : base(logger, + commandValidator, + messageValidator, + metricsProcessor) + { + _jobManager = jobManager; + + var responseMarkup = Init(layoutSupplier, layoutParser); + + _options = SendOptionsBuilder.CreateBuilder(responseMarkup); + } + + private static TReplyMarkup Init(ILayoutSupplier layoutSupplier, ILayoutParser layoutParser) + { var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; var responseLayout = layoutParser.ParseFromFile(Path.Combine(location, "main_layout.json")); var responseMarkup = layoutSupplier.GetMarkup(responseLayout); - _options = SendOptionsBuilder.CreateBuilder(responseMarkup); + return responseMarkup; + } + + public override void SetBot(IBot bot) + { + base.SetBot(bot); + _bot = bot; } protected override Task InnerProcessContact(Message message, CancellationToken token) @@ -62,64 +94,64 @@ protected override async Task InnerProcess(Message message, CancellationToken to { Message = new Message { - Uid = Guid.NewGuid().ToString(), ChatIds = message.ChatIds, Body = "Bot started..." } }; - await Bot.SendMessageAsync(greetingMessageRequest, _options, token); + await SendMessage(greetingMessageRequest, _options, token); var assemblyPath = Path.GetDirectoryName(typeof(StartCommandProcessor).Assembly.Location) ?? throw new FileNotFoundException(); - _jobManager.AddJob(Bot, - new Reliability - { - IsEnabled = false, - Delay = TimeSpan.FromSeconds(3), - IsExponential = true, - MaxTries = 5 - }, - new Message - { - Body = "Now you see me!", - ChatIds = [chatId], - Contact = new Contact + if (_bot != null) + _jobManager.AddJob(_bot, + new Reliability { - Phone = "+9003289384923842343243243", - Name = "Test", - Surname = "Botticelli" + IsEnabled = false, + Delay = TimeSpan.FromSeconds(3), + IsExponential = true, + MaxTries = 5 }, - Attachments = - [ - new BinaryBaseAttachment(Guid.NewGuid().ToString(), - "testpic.png", - MediaType.Image, - string.Empty, - await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/testpic.png"), token)), - - new BinaryBaseAttachment(Guid.NewGuid().ToString(), - "voice.mp3", - MediaType.Voice, - string.Empty, - await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/voice.mp3"), token)), - - new BinaryBaseAttachment(Guid.NewGuid().ToString(), - "video.mp4", - MediaType.Video, - string.Empty, - await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/video.mp4"), token)), - - new BinaryBaseAttachment(Guid.NewGuid().ToString(), - "document.odt", - MediaType.Document, - string.Empty, - await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/document.odt"), token)) - ] - }, - new Schedule - { - Cron = "*/30 * * ? * * *" - }); + new Message + { + Body = "Now you see me!", + ChatIds = [chatId], + Contact = new Contact + { + Phone = "+9003289384923842343243243", + Name = "Test", + Surname = "Botticelli" + }, + Attachments = + [ + new BinaryBaseAttachment(Guid.NewGuid().ToString(), + "testpic.png", + MediaType.Image, + string.Empty, + await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/testpic.png"), token)), + + new BinaryBaseAttachment(Guid.NewGuid().ToString(), + "voice.mp3", + MediaType.Voice, + string.Empty, + await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/voice.mp3"), token)), + + new BinaryBaseAttachment(Guid.NewGuid().ToString(), + "video.mp4", + MediaType.Video, + string.Empty, + await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/video.mp4"), token)), + + new BinaryBaseAttachment(Guid.NewGuid().ToString(), + "document.odt", + MediaType.Document, + string.Empty, + await File.ReadAllBytesAsync(Path.Combine(assemblyPath, "Media/document.odt"), token)) + ] + }, + new Schedule + { + Cron = "*/30 * * ? * * *" + }); } } \ No newline at end of file diff --git a/Samples/Messaging.Sample.Common/Commands/Processors/StopCommandProcessor.cs b/Samples/Messaging.Sample.Common/Commands/Processors/StopCommandProcessor.cs index bf30433f..f780b5d4 100644 --- a/Samples/Messaging.Sample.Common/Commands/Processors/StopCommandProcessor.cs +++ b/Samples/Messaging.Sample.Common/Commands/Processors/StopCommandProcessor.cs @@ -1,8 +1,8 @@ using System.Reflection; using Botticelli.Client.Analytics; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Commands.Processors; using Botticelli.Framework.Commands.Validators; -using Botticelli.Framework.Controls.Parsers; using Botticelli.Framework.SendOptions; using Botticelli.Scheduler.Interfaces; using Botticelli.Shared.API.Client.Requests; @@ -16,21 +16,40 @@ public class StopCommandProcessor : CommandProcessor where TReplyMarkup : class { private readonly IJobManager _jobManager; - private readonly SendOptionsBuilder? _options; + private SendOptionsBuilder? _options; public StopCommandProcessor(ILogger> logger, ICommandValidator commandValidator, - MetricsProcessor metricsProcessor, IJobManager jobManager, ILayoutSupplier layoutSupplier, ILayoutParser layoutParser, IValidator messageValidator) : base(logger, commandValidator, - metricsProcessor, messageValidator) { _jobManager = jobManager; + Init(layoutSupplier, layoutParser); + } + + public StopCommandProcessor(ILogger> logger, + ICommandValidator commandValidator, + IJobManager jobManager, + ILayoutSupplier layoutSupplier, + ILayoutParser layoutParser, + IValidator messageValidator, + MetricsProcessor? metricsProcessor) + : base(logger, + commandValidator, + messageValidator, + metricsProcessor) + { + _jobManager = jobManager; + Init(layoutSupplier, layoutParser); + } + + private void Init(ILayoutSupplier layoutSupplier, ILayoutParser layoutParser) + { var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; var responseLayout = layoutParser.ParseFromFile(Path.Combine(location, "start_layout.json")); var responseMarkup = layoutSupplier.GetMarkup(responseLayout); @@ -68,6 +87,6 @@ protected override async Task InnerProcess(Message message, CancellationToken to } }; - await Bot.SendMessageAsync(farewellMessageRequest, _options, token); + await SendMessage(farewellMessageRequest, _options, token); } } \ No newline at end of file diff --git a/Samples/Messaging.Sample.Common/Messaging.Sample.Common.csproj b/Samples/Messaging.Sample.Common/Messaging.Sample.Common.csproj index d6857265..0ee43464 100644 --- a/Samples/Messaging.Sample.Common/Messaging.Sample.Common.csproj +++ b/Samples/Messaging.Sample.Common/Messaging.Sample.Common.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli diff --git a/Samples/Messaging.Sample.Telegram/Messaging.Sample.Telegram.csproj b/Samples/Messaging.Sample.Telegram/Messaging.Sample.Telegram.csproj index 802e34c6..02f93558 100644 --- a/Samples/Messaging.Sample.Telegram/Messaging.Sample.Telegram.csproj +++ b/Samples/Messaging.Sample.Telegram/Messaging.Sample.Telegram.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,6 +24,7 @@ + diff --git a/Samples/Messaging.Sample.Telegram/Program.cs b/Samples/Messaging.Sample.Telegram/Program.cs index cd116d42..8fab3ca1 100644 --- a/Samples/Messaging.Sample.Telegram/Program.cs +++ b/Samples/Messaging.Sample.Telegram/Program.cs @@ -10,21 +10,24 @@ var builder = WebApplication.CreateBuilder(args); builder.Services - .AddTelegramBot(builder.Configuration) - .AddTelegramLayoutsSupport() - .AddLogging(cfg => cfg.AddNLog()) - .AddQuartzScheduler(builder.Configuration); + .AddTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services + .AddTelegramLayoutsSupport() + .AddLogging(cfg => cfg.AddNLog()) + .AddQuartzScheduler(builder.Configuration); builder.Services.AddBotCommand() - .AddProcessor>() - .AddValidator>(); + .AddProcessor>() + .AddValidator>(); builder.Services.AddBotCommand() - .AddProcessor>() - .AddValidator>(); + .AddProcessor>() + .AddValidator>(); builder.Services.AddBotCommand() - .AddProcessor>() - .AddValidator>(); + .AddProcessor>() + .AddValidator>(); -builder.Build().Run(); \ No newline at end of file +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Samples/Messaging.Sample.Telegram/appsettings.json b/Samples/Messaging.Sample.Telegram/appsettings.json index ec02b8a1..04e75a06 100644 --- a/Samples/Messaging.Sample.Telegram/appsettings.json +++ b/Samples/Messaging.Sample.Telegram/appsettings.json @@ -20,5 +20,11 @@ "useTestEnvironment": false, "name": "TestBot" }, + "Broadcasting": { + "BroadcastingDbConnectionString": "YourConnectionStringHere", + "BotId": "XuS3Ok5dP37v06vrqvfsfXT5G0LVq0nyD68Y", + "HowOld": "00:10:00", + "ServerUri": "http://103.252.116.18:5042/v1" + }, "AllowedHosts": "*" } \ No newline at end of file diff --git a/Samples/Monads.Sample.Telegram/Commands/MathCommand.cs b/Samples/Monads.Sample.Telegram/Commands/MathCommand.cs index 79a3f3a8..3012a44b 100644 --- a/Samples/Monads.Sample.Telegram/Commands/MathCommand.cs +++ b/Samples/Monads.Sample.Telegram/Commands/MathCommand.cs @@ -1,4 +1,5 @@ -using Botticelli.Framework.Monads.Commands.Context; +using Botticelli.Chained.Context; +using Botticelli.Chained.Monads.Commands; namespace TelegramMonadsBasedBot.Commands; diff --git a/Samples/Monads.Sample.Telegram/Monads.Sample.Telegram.csproj b/Samples/Monads.Sample.Telegram/Monads.Sample.Telegram.csproj index f385c72e..e383a96f 100644 --- a/Samples/Monads.Sample.Telegram/Monads.Sample.Telegram.csproj +++ b/Samples/Monads.Sample.Telegram/Monads.Sample.Telegram.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -18,7 +18,8 @@ - + + diff --git a/Samples/Monads.Sample.Telegram/Program.cs b/Samples/Monads.Sample.Telegram/Program.cs index 7e55ccda..a4801dc2 100644 --- a/Samples/Monads.Sample.Telegram/Program.cs +++ b/Samples/Monads.Sample.Telegram/Program.cs @@ -1,22 +1,29 @@ +using Botticelli.Chained.Context.Redis.Extensions; +using Botticelli.Chained.Monads.Commands.Processors; +using Botticelli.Chained.Monads.Extensions; using Botticelli.Framework.Commands.Validators; using Botticelli.Framework.Extensions; -using Botticelli.Framework.Monads.Commands.Processors; -using Botticelli.Framework.Monads.Extensions; using Botticelli.Framework.Telegram.Extensions; using Botticelli.Framework.Telegram.Layout; +using Botticelli.Interfaces; using NLog.Extensions.Logging; using Telegram.Bot.Types.ReplyMarkups; using TelegramMonadsBasedBot.Commands; var builder = WebApplication.CreateBuilder(args); +var bot = builder.Services + .AddTelegramBot(builder.Configuration) + .Prepare(); + builder.Services - .AddTelegramBot(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) - .AddTelegramLayoutsSupport(); + .AddLogging(cfg => cfg.AddNLog()) + .AddTelegramLayoutsSupport(); -builder.Services.AddBotCommand() - .AddMonadsChain, ReplyKeyboardMarkup, ReplyTelegramLayoutSupplier>(builder.Services, +builder.Services + .AddChainedRedisStorage(builder.Configuration) + .AddBotCommand() + .AddMonadsChain, ReplyKeyboardMarkup, ReplyTelegramLayoutSupplier>(builder.Services, cb => cb.Next>() .Next>(tp => tp.SuccessFunc = Math.Sqrt) @@ -32,4 +39,4 @@ var app = builder.Build(); -app.Run(); \ No newline at end of file +await app.RunAsync(); \ No newline at end of file diff --git a/Samples/Monads.Sample.Telegram/appsettings.json b/Samples/Monads.Sample.Telegram/appsettings.json index 7ed160ca..a9619f7a 100644 --- a/Samples/Monads.Sample.Telegram/appsettings.json +++ b/Samples/Monads.Sample.Telegram/appsettings.json @@ -28,5 +28,8 @@ "speed": 0.0, "compressionLevel": 0 }, + "RedisStorageSettings": { + "ConnectionString": "localhost:6379" + }, "AllowedHosts": "*" } \ No newline at end of file diff --git a/Samples/TelegramAiSample/Ai.ChatGpt.Sample.Telegram.csproj b/Samples/TelegramAiSample/Ai.ChatGpt.Sample.Telegram.csproj index eeaf9843..bf57098d 100644 --- a/Samples/TelegramAiSample/Ai.ChatGpt.Sample.Telegram.csproj +++ b/Samples/TelegramAiSample/Ai.ChatGpt.Sample.Telegram.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.7.0 + 0.8.0 Botticelli Igor Evdokimov https://github.com/devgopher/botticelli diff --git a/Samples/TelegramAiSample/Program.cs b/Samples/TelegramAiSample/Program.cs index 362be32d..ca360322 100644 --- a/Samples/TelegramAiSample/Program.cs +++ b/Samples/TelegramAiSample/Program.cs @@ -15,17 +15,19 @@ var builder = WebApplication.CreateBuilder(args); builder.Services - .AddTelegramBot(builder.Configuration) - .AddLogging(cfg => cfg.AddNLog()) - .AddChatGptProvider(builder.Configuration) - .AddAiValidation() - .AddScoped, PassValidator>() - .AddSingleton() - .UsePassBusAgent, AiHandler>() - .UsePassBusClient>() - .UsePassEventBusClient>() - .AddBotCommand, PassValidator>(); + .AddTelegramBot(builder.Configuration) + .Prepare(); + +builder.Services.AddLogging(cfg => cfg.AddNLog()) + .AddChatGptProvider(builder.Configuration) + .AddAiValidation() + .AddScoped, PassValidator>() + .AddSingleton() + .UsePassBusAgent, AiHandler>() + .UsePassBusClient>() + .UsePassEventBusClient>() + .AddBotCommand, PassValidator>(); var app = builder.Build(); -app.Run(); \ No newline at end of file +await app.RunAsync(); \ No newline at end of file diff --git a/Samples/Ai.Messaging.Sample.Vk/appsettings.json b/Samples/TelegramAiSample/appsettings.json similarity index 66% rename from Samples/Ai.Messaging.Sample.Vk/appsettings.json rename to Samples/TelegramAiSample/appsettings.json index 40e760c4..b9677c7e 100644 --- a/Samples/Ai.Messaging.Sample.Vk/appsettings.json +++ b/Samples/TelegramAiSample/appsettings.json @@ -12,11 +12,14 @@ "ServerUri": "http://113.30.189.83:5042/v1/" }, "AnalyticsClient": { - "TargetUrl": "http://113.30.189.83:5251/v1/" + "TargetUrl": "http://localhost:5251/v1/" }, - "VkBot": { - "PollIntervalMs": 100, - "GroupId": 225327325 + "TelegramBot": { + "timeout": 60, + "useThrottling": true, + "useTestEnvironment": false, + "name": "TestBot" }, "AllowedHosts": "*" -} \ No newline at end of file +} + diff --git a/Tests/Botticelli.Chained.Context.InMemory.Tests/Botticelli.Chained.Context.InMemory.Tests.csproj b/Tests/Botticelli.Chained.Context.InMemory.Tests/Botticelli.Chained.Context.InMemory.Tests.csproj new file mode 100644 index 00000000..d7fc6702 --- /dev/null +++ b/Tests/Botticelli.Chained.Context.InMemory.Tests/Botticelli.Chained.Context.InMemory.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Botticelli.Chained.Context.InMemory.Tests/InMemoryStorageTests.cs b/Tests/Botticelli.Chained.Context.InMemory.Tests/InMemoryStorageTests.cs new file mode 100644 index 00000000..85863ac7 --- /dev/null +++ b/Tests/Botticelli.Chained.Context.InMemory.Tests/InMemoryStorageTests.cs @@ -0,0 +1,82 @@ +using FluentAssertions; + +namespace Botticelli.Chained.Context.InMemory.Tests; + +[TestFixture] +public class InMemoryStorageTests +{ + [SetUp] + public void SetUp() + { + _storage = new InMemoryStorage(); + } + + private InMemoryStorage _storage; + + [Test] + [TestCase("testKey", 128923)] + public void Add_ShouldAddValue_WhenKeyIsNew(string key, int value) + { + // Act + _storage.Add(key, value); + + // Assert + _storage.ContainsKey(key).Should().BeTrue(); + _storage[key].Should().Be(value); + } + + [Test] + [TestCase("testKey", 1343223)] + public void Remove_ShouldRemoveValue_WhenKeyExists(string key, int value) + { + // Arrange + _storage.Add(key, value); + + // Act + var result = _storage.Remove(key); + + // Assert + result.Should().BeTrue(); + _storage.ContainsKey(key).Should().BeFalse(); + } + + [Test] + [TestCase("testKey", 33432)] + public void TryGetValue_ShouldReturnTrueAndValue_WhenKeyExists(string key, int value) + { + // Arrange + _storage.Add(key, value); + + // Act + var result = _storage.TryGetValue(key, out var retrievedValue); + + // Assert + result.Should().BeTrue(); + retrievedValue.Should().Be(value); + } + + [Test] + public void TryGetValue_ShouldReturnFalse_WhenKeyDoesNotExist() + { + // Arrange + var key = "nonExistentKey"; + + // Act + var result = _storage.TryGetValue(key, out var retrievedValue); + + // Assert + result.Should().BeFalse(); + retrievedValue.Should().Be(default); + } + + [Test] + [TestCase("testKey", 33432)] + public void Indexer_ShouldGetAndSetValue(string key, int value) + { + // Act + _storage[key] = value; + + // Assert + _storage[key].Should().Be(value); + } +} \ No newline at end of file diff --git a/Tests/Botticelli.Chained.Context.Redis.Tests/Botticelli.Chained.Context.Redis.Tests.csproj b/Tests/Botticelli.Chained.Context.Redis.Tests/Botticelli.Chained.Context.Redis.Tests.csproj new file mode 100644 index 00000000..f313d82f --- /dev/null +++ b/Tests/Botticelli.Chained.Context.Redis.Tests/Botticelli.Chained.Context.Redis.Tests.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + + false + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Botticelli.Chained.Context.Redis.Tests/RedisStorageTests.cs b/Tests/Botticelli.Chained.Context.Redis.Tests/RedisStorageTests.cs new file mode 100644 index 00000000..881baeb4 --- /dev/null +++ b/Tests/Botticelli.Chained.Context.Redis.Tests/RedisStorageTests.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using StackExchange.Redis; + +namespace Botticelli.Chained.Context.Redis.Tests; + +[TestFixture] +public class RedisStorageTests +{ + private Mock _mockDatabase; + private RedisStorage _redisStorage; + private Dictionary _storage; + + [SetUp] + public void SetUp() + { + _mockDatabase = new Mock(); + var mockConnection = new Mock(); + mockConnection.Setup(m => m.GetDatabase(It.IsAny(), It.IsAny())).Returns(_mockDatabase.Object); + + _redisStorage = new RedisStorage(_mockDatabase.Object); + + // Setup the mock to maintain state + _mockDatabase.Setup(m => m.StringSet(It.IsAny(), It.IsAny(), null, When.Always, CommandFlags.None)) + .Returns((key, + value, + _, + _, + _) => + { + _storage[key] = value.ToString(); + return true; + }); + + _mockDatabase.Setup(m => m.StringGet(It.IsAny(), It.IsAny())) + .Returns((string key, CommandFlags _) => _storage.TryGetValue(key, out var val) ? val : RedisValue.Null); + _mockDatabase.Setup(m => m.KeyExists(It.IsAny(), It.IsAny())) + .Returns((string key, CommandFlags _) => _storage.ContainsKey(key)); + } + + [Test] + public void Add_ShouldAddValue_WhenKeyIsNew() + { + // Arrange + var key = "testKey"; + var value = "Test"; + + // Act + Assert.DoesNotThrow(() => _redisStorage.Add(key, value)); + } + + [Test] + public void Remove_ShouldRemoveValue_WhenKeyExists() + { + // Arrange + var key = "testKey"; + _mockDatabase.Setup(m => m.KeyDelete(key, CommandFlags.None)).Returns(true); + + // Act + var result = _redisStorage.Remove(key); + + // Assert + result.Should().BeTrue(); + _mockDatabase.Verify(m => m.KeyDelete(key, CommandFlags.None), Times.Once); + } + + [Test] + public void TryGetValue_ShouldReturnTrueAndValue_WhenKeyExists() + { + // Arrange + var key = "testKey"; + var value = "Test"; + _mockDatabase.Setup(m => m.StringGet(key, CommandFlags.None)).Returns(JsonSerializer.Serialize(value)); + _mockDatabase.Setup(m => m.KeyExists(key, CommandFlags.None)).Returns(true); + + // Act + var result = _redisStorage.TryGetValue(key, out var retrievedValue); + + // Assert + result.Should().BeTrue(); + retrievedValue.Should().BeEquivalentTo(value); + } + + [Test] + public void TryGetValue_ShouldReturnFalse_WhenKeyDoesNotExist() + { + // Arrange + var key = "nonExistentKey"; + _mockDatabase.Setup(m => m.KeyExists(key, CommandFlags.None)).Returns(false); + + // Act + var result = _redisStorage.TryGetValue(key, out var retrievedValue); + + // Assert + result.Should().BeFalse(); + retrievedValue.Should().BeNull(); + } +} \ No newline at end of file diff --git a/Tests/Botticelli.Framework.Controls.Tests/Botticelli.Framework.Controls.Tests.csproj b/Tests/Botticelli.Controls.Tests/Botticelli.Controls.Tests.csproj similarity index 93% rename from Tests/Botticelli.Framework.Controls.Tests/Botticelli.Framework.Controls.Tests.csproj rename to Tests/Botticelli.Controls.Tests/Botticelli.Controls.Tests.csproj index 432636dd..3c694055 100644 --- a/Tests/Botticelli.Framework.Controls.Tests/Botticelli.Framework.Controls.Tests.csproj +++ b/Tests/Botticelli.Controls.Tests/Botticelli.Controls.Tests.csproj @@ -37,7 +37,7 @@ - + diff --git a/Tests/Botticelli.Framework.Controls.Tests/Layouts/JsonLayoutParserTest.cs b/Tests/Botticelli.Controls.Tests/Layouts/JsonLayoutParserTest.cs similarity index 80% rename from Tests/Botticelli.Framework.Controls.Tests/Layouts/JsonLayoutParserTest.cs rename to Tests/Botticelli.Controls.Tests/Layouts/JsonLayoutParserTest.cs index a2ec571e..92d47951 100644 --- a/Tests/Botticelli.Framework.Controls.Tests/Layouts/JsonLayoutParserTest.cs +++ b/Tests/Botticelli.Controls.Tests/Layouts/JsonLayoutParserTest.cs @@ -1,7 +1,7 @@ -using Botticelli.Framework.Controls.Exceptions; -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Exceptions; +using Botticelli.Controls.Parsers; -namespace Botticelli.Framework.Controls.Tests.Layouts; +namespace Botticelli.Controls.Tests.Layouts; [TestFixture] [TestOf(typeof(JsonLayoutParser))] diff --git a/Tests/Botticelli.Framework.Controls.Tests/Layouts/ReplyTelegramLayoutSupplierTest.cs b/Tests/Botticelli.Controls.Tests/Layouts/ReplyTelegramLayoutSupplierTest.cs similarity index 85% rename from Tests/Botticelli.Framework.Controls.Tests/Layouts/ReplyTelegramLayoutSupplierTest.cs rename to Tests/Botticelli.Controls.Tests/Layouts/ReplyTelegramLayoutSupplierTest.cs index 44be131d..9c16165b 100644 --- a/Tests/Botticelli.Framework.Controls.Tests/Layouts/ReplyTelegramLayoutSupplierTest.cs +++ b/Tests/Botticelli.Controls.Tests/Layouts/ReplyTelegramLayoutSupplierTest.cs @@ -1,7 +1,7 @@ -using Botticelli.Framework.Controls.Parsers; +using Botticelli.Controls.Parsers; using Botticelli.Framework.Telegram.Layout; -namespace Botticelli.Framework.Controls.Tests.Layouts; +namespace Botticelli.Controls.Tests.Layouts; [TestFixture] [TestOf(typeof(ReplyTelegramLayoutSupplier))] diff --git a/Tests/Botticelli.Framework.Controls.Tests/TestCases/CorrectLayout.json b/Tests/Botticelli.Controls.Tests/TestCases/CorrectLayout.json similarity index 100% rename from Tests/Botticelli.Framework.Controls.Tests/TestCases/CorrectLayout.json rename to Tests/Botticelli.Controls.Tests/TestCases/CorrectLayout.json diff --git a/Tests/Botticelli.Framework.Controls.Tests/TestCases/InvalidLayout.json b/Tests/Botticelli.Controls.Tests/TestCases/InvalidLayout.json similarity index 100% rename from Tests/Botticelli.Framework.Controls.Tests/TestCases/InvalidLayout.json rename to Tests/Botticelli.Controls.Tests/TestCases/InvalidLayout.json diff --git a/Tests/Botticelli.Framework.Vk.Tests/Botticelli.Framework.Vk.Tests.csproj b/Tests/Botticelli.Framework.Vk.Tests/Botticelli.Framework.Vk.Tests.csproj deleted file mode 100644 index 4634f82b..00000000 --- a/Tests/Botticelli.Framework.Vk.Tests/Botticelli.Framework.Vk.Tests.csproj +++ /dev/null @@ -1,45 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - - PreserveNewest - true - PreserveNewest - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - diff --git a/Tests/Botticelli.Framework.Vk.Tests/EnvironmentDataProvider.cs b/Tests/Botticelli.Framework.Vk.Tests/EnvironmentDataProvider.cs deleted file mode 100644 index 678d4f9f..00000000 --- a/Tests/Botticelli.Framework.Vk.Tests/EnvironmentDataProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Botticelli.Framework.Vk.Tests; - -internal static class EnvironmentDataProvider -{ - public static string GetApiKey() - { - return Environment.GetEnvironmentVariable("TEST_VK_API_KEY") ?? "test_empty_key"; - } - - public static int GetTargetUserId() - { - return int.Parse(Environment.GetEnvironmentVariable("TEST_VK_TARGET_USER_ID") ?? "-1"); - } -} \ No newline at end of file diff --git a/Tests/Botticelli.Framework.Vk.Tests/LongPollMessagesProviderTests.cs b/Tests/Botticelli.Framework.Vk.Tests/LongPollMessagesProviderTests.cs deleted file mode 100644 index 201b4c1f..00000000 --- a/Tests/Botticelli.Framework.Vk.Tests/LongPollMessagesProviderTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Botticelli.Framework.Vk.Messages; -using Botticelli.Framework.Vk.Messages.Options; -using Botticelli.Shared.Utils; -using NUnit.Framework; -using Shared; - -namespace Botticelli.Framework.Vk.Tests; - -[TestFixture] -public class LongPollMessagesProviderTests -{ - [SetUp] - public void Setup() - { - _provider = new LongPollMessagesProvider(new OptionsMonitorMock(new VkBotSettings - { - Name = "test", - PollIntervalMs = 500, - GroupId = 221973506 - }).CurrentValue, - new TestHttpClientFactory(), - LoggerMocks.CreateConsoleLogger()); - } - - private LongPollMessagesProvider? _provider; - - [Test] - public async Task StartTest() - { - _provider.NotNull(); - await _provider!.Stop(); - _provider.SetApiKey(EnvironmentDataProvider.GetApiKey()); - - var task = Task.Run(() => _provider.Start(CancellationToken.None)); - - Thread.Sleep(5000); - - Assert.That(task.Exception == null); - } - - [Test] - public void StopTest() - { - _provider.NotNull(); - _ = _provider!.Start(CancellationToken.None); - - Thread.Sleep(2000); - - Assert.DoesNotThrowAsync(_provider.Stop); - } -} \ No newline at end of file diff --git a/Tests/Botticelli.Framework.Vk.Tests/MessagePublisherTests.cs b/Tests/Botticelli.Framework.Vk.Tests/MessagePublisherTests.cs deleted file mode 100644 index 628c7c7c..00000000 --- a/Tests/Botticelli.Framework.Vk.Tests/MessagePublisherTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Globalization; -using Botticelli.Framework.Vk.Messages; -using Botticelli.Framework.Vk.Messages.API.Requests; -using NUnit.Framework; -using Shared; - -namespace Botticelli.Framework.Vk.Tests; - -[TestFixture] -public class MessagePublisherTests(MessagePublisher publisher) -{ - [SetUp] - public void Setup() - { - _publisher = new MessagePublisher(new TestHttpClientFactory(), - LoggerMocks.CreateConsoleLogger()); - } - - private MessagePublisher _publisher = publisher; - - [Test] - public Task SendAsyncTest() - { - _publisher.SetApiKey(EnvironmentDataProvider.GetApiKey()); - Assert.DoesNotThrowAsync(async () => await _publisher.SendAsync(new VkSendMessageRequest - { - AccessToken = EnvironmentDataProvider.GetApiKey(), - Body = $"test msg {DateTime.Now.ToString(CultureInfo.InvariantCulture)}", - UserId = EnvironmentDataProvider.GetTargetUserId().ToString() - }, - CancellationToken.None)); - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Tests/Botticelli.Framework.Vk.Tests/TestHttpClientFactory.cs b/Tests/Botticelli.Framework.Vk.Tests/TestHttpClientFactory.cs deleted file mode 100644 index b3f187c1..00000000 --- a/Tests/Botticelli.Framework.Vk.Tests/TestHttpClientFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Text.Json; -using Botticelli.Framework.Vk.Messages.API.Responses; -using Botticelli.Framework.Vk.Messages.API.Utils; -using RichardSzalay.MockHttp; - -namespace Botticelli.Framework.Vk.Tests; - -internal class TestHttpClientFactory : IHttpClientFactory -{ - public HttpClient CreateClient(string name) - { - var mockHttp = new MockHttpMessageHandler(); - - mockHttp.When(ApiUtils.GetMethodUri("https://api.vk.com", - "messages.send") - .ToString()) - .Respond("application/json", "{'Result' : 'OK'}"); - - var mockResponse = new GetMessageSessionDataResponse - { - Response = new SessionDataResponse - { - Server = "https://test.mock", - Key = "test_key", - Ts = "12323213123" - } - }; - - mockHttp.When(ApiUtils.GetMethodUri("https://api.vk.com", "groups.getLongPollServer").ToString()) - .Respond("application/json", JsonSerializer.Serialize(mockResponse)); - - return mockHttp.ToHttpClient(); - } -} \ No newline at end of file diff --git a/Tests/Botticelli.Framework.Vk.Tests/appsettings.json b/Tests/Botticelli.Framework.Vk.Tests/appsettings.json deleted file mode 100644 index aa81a1f4..00000000 --- a/Tests/Botticelli.Framework.Vk.Tests/appsettings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "SampleSettings": { - "SecureStorageConnectionString": "Filename=database.db;Password=123;ReadOnly=false" - }, - "AllowedHosts": "*" -} \ No newline at end of file diff --git a/Tests/MetricsRandomGenerator/MetricsRandomGenerator.csproj b/Tests/MetricsRandomGenerator/MetricsRandomGenerator.csproj index 7435c120..d536bc17 100644 --- a/Tests/MetricsRandomGenerator/MetricsRandomGenerator.csproj +++ b/Tests/MetricsRandomGenerator/MetricsRandomGenerator.csproj @@ -12,9 +12,9 @@ - - appsettings.json - + + appsettings.json + diff --git a/Tests/Shared/HttpClientFactoryMock.cs b/Tests/Mocks/HttpClientFactoryMock.cs similarity index 94% rename from Tests/Shared/HttpClientFactoryMock.cs rename to Tests/Mocks/HttpClientFactoryMock.cs index d4e59781..e063f502 100644 --- a/Tests/Shared/HttpClientFactoryMock.cs +++ b/Tests/Mocks/HttpClientFactoryMock.cs @@ -1,4 +1,4 @@ -namespace Shared; +namespace Mocks; public class HttpClientFactoryMock : IHttpClientFactory { diff --git a/Tests/Shared/LoggerMocks.cs b/Tests/Mocks/LoggerMocks.cs similarity index 93% rename from Tests/Shared/LoggerMocks.cs rename to Tests/Mocks/LoggerMocks.cs index 80df9969..a86b9f14 100644 --- a/Tests/Shared/LoggerMocks.cs +++ b/Tests/Mocks/LoggerMocks.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -namespace Shared; +namespace Mocks; public static class LoggerMocks { diff --git a/Tests/Shared/Shared.csproj b/Tests/Mocks/Mocks.csproj similarity index 100% rename from Tests/Shared/Shared.csproj rename to Tests/Mocks/Mocks.csproj diff --git a/Tests/Shared/OptionsMock.cs b/Tests/Mocks/OptionsMock.cs similarity index 91% rename from Tests/Shared/OptionsMock.cs rename to Tests/Mocks/OptionsMock.cs index dada66f5..f7340d74 100644 --- a/Tests/Shared/OptionsMock.cs +++ b/Tests/Mocks/OptionsMock.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Options; -namespace Shared; +namespace Mocks; public class OptionsMock : IOptions where T : class { diff --git a/Tests/Shared/OptionsMonitorMock.cs b/Tests/Mocks/OptionsMonitorMock.cs similarity index 95% rename from Tests/Shared/OptionsMonitorMock.cs rename to Tests/Mocks/OptionsMonitorMock.cs index 58b13585..7b315c59 100644 --- a/Tests/Shared/OptionsMonitorMock.cs +++ b/Tests/Mocks/OptionsMonitorMock.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Options; -namespace Shared; +namespace Mocks; public class OptionsMonitorMock : IOptionsMonitor { diff --git a/Viber.Api/Entities/Location.cs b/Viber.Api/Entities/Location.cs deleted file mode 100644 index bdaef7bd..00000000 --- a/Viber.Api/Entities/Location.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Entities -{ - public class Location - { - [JsonPropertyName("lat")] - public double Lat { get; set; } - - [JsonPropertyName("lon")] - public double Lon { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Entities/MessageSender.cs b/Viber.Api/Entities/MessageSender.cs deleted file mode 100644 index 61c93ed2..00000000 --- a/Viber.Api/Entities/MessageSender.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Entities -{ - public class MessageSender - { - [JsonPropertyName("id")] - public string? Id { get; set; } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("avatar")] - public string? Avatar { get; set; } - - [JsonPropertyName("country")] - public string? Country { get; set; } - - [JsonPropertyName("language")] - public string? Language { get; set; } - - [JsonPropertyName("api_version")] - public int ApiVersion { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Entities/Sender.cs b/Viber.Api/Entities/Sender.cs deleted file mode 100644 index 288e15f1..00000000 --- a/Viber.Api/Entities/Sender.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Entities -{ - public class Sender - { - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("avatar")] - public string? Avatar { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Entities/User.cs b/Viber.Api/Entities/User.cs deleted file mode 100644 index f286be4b..00000000 --- a/Viber.Api/Entities/User.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Entities -{ - public class User - { - [JsonPropertyName("id")] - public string? Id { get; set; } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("avatar")] - public string? Avatar { get; set; } - - [JsonPropertyName("country")] - public string? Country { get; set; } - - [JsonPropertyName("language")] - public string? Language { get; set; } - - [JsonPropertyName("api_version")] - public int ApiVersion { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Entities/ViberMessage.cs b/Viber.Api/Entities/ViberMessage.cs deleted file mode 100644 index 35032ae7..00000000 --- a/Viber.Api/Entities/ViberMessage.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Entities -{ - public class ViberMessage - { - [JsonPropertyName("type")] - public string? Type { get; set; } - - [JsonPropertyName("text")] - public string? Text { get; set; } - - [JsonPropertyName("media")] - public string? Media { get; set; } - - [JsonPropertyName("location")] - public Location? Location { get; set; } - - [JsonPropertyName("tracking_data")] - public string? TrackingData { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Exceptions/ViberClientException.cs b/Viber.Api/Exceptions/ViberClientException.cs deleted file mode 100644 index 82f05fdd..00000000 --- a/Viber.Api/Exceptions/ViberClientException.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Viber.Api.Exceptions -{ - public class ViberClientException : Exception - { - public ViberClientException(string message, Exception? inner = null) : base(message, inner) - { - } - } -} \ No newline at end of file diff --git a/Viber.Api/IViberService.cs b/Viber.Api/IViberService.cs deleted file mode 100644 index ab2b7e62..00000000 --- a/Viber.Api/IViberService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Viber.Api.Requests; -using Viber.Api.Responses; - -namespace Viber.Api -{ - public interface IViberService : IDisposable - { - delegate void GotMessageHandler(GetWebHookEvent @event); - - event GotMessageHandler GotMessage; - - void Start(); - - void Stop(); - - Task SetWebHook(SetWebHookRequest request, - CancellationToken cancellationToken = default); - - Task SendMessage(ApiSendMessageRequest request, - CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/Viber.Api/Requests/ApiSendMessageRequest.cs b/Viber.Api/Requests/ApiSendMessageRequest.cs deleted file mode 100644 index 59a6b500..00000000 --- a/Viber.Api/Requests/ApiSendMessageRequest.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Serialization; -using Viber.Api.Entities; - -namespace Viber.Api.Requests -{ - public class ApiSendMessageRequest : BaseRequest - { - [JsonPropertyName("receiver")] - public string? Receiver { get; set; } - - [JsonPropertyName("min_api_version")] - public int MinApiVersion { get; set; } - - [JsonPropertyName("sender")] - public Sender? Sender { get; set; } - - [JsonPropertyName("tracking_data")] - public string? TrackingData { get; set; } - - [JsonPropertyName("type")] - public string? Type { get; set; } - - [JsonPropertyName("text")] - public string? Text { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Requests/BaseRequest.cs b/Viber.Api/Requests/BaseRequest.cs deleted file mode 100644 index 3f96fbac..00000000 --- a/Viber.Api/Requests/BaseRequest.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Requests -{ - public abstract class BaseRequest - { - [JsonPropertyName("auth_token")] - public string? AuthToken { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Requests/RemoveWebHookRequest.cs b/Viber.Api/Requests/RemoveWebHookRequest.cs deleted file mode 100644 index 0d16b0b8..00000000 --- a/Viber.Api/Requests/RemoveWebHookRequest.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Requests -{ - public class RemoveWebHookRequest : BaseRequest - { - [JsonPropertyName("url")] - public string? Url { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Requests/SetWebHookRequest.cs b/Viber.Api/Requests/SetWebHookRequest.cs deleted file mode 100644 index 37433c93..00000000 --- a/Viber.Api/Requests/SetWebHookRequest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Viber.Api.Requests -{ - public class SetWebHookRequest : BaseRequest - { - [JsonPropertyName("url")] - public string? Url { get; set; } - - [JsonPropertyName("event_types")] - public List? EventTypes { get; set; } - - [JsonPropertyName("send_name")] - public bool SendName { get; set; } - - [JsonPropertyName("send_photo")] - public bool SendPhoto { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Responses/ApiSendMessageResponse.cs b/Viber.Api/Responses/ApiSendMessageResponse.cs deleted file mode 100644 index 89b35101..00000000 --- a/Viber.Api/Responses/ApiSendMessageResponse.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Viber.Api.Responses -{ - public class ApiSendMessageResponse - { - [JsonPropertyName("status")] - public int Status { get; set; } - - [JsonPropertyName("status_message")] - public string? StatusMessage { get; set; } - - [JsonPropertyName("message_token")] - public long MessageToken { get; set; } - - [JsonPropertyName("chat_hostname")] - public string? ChatHostname { get; set; } - - [JsonPropertyName("billing_status")] - public int BillingStatus { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Responses/GetWebHookEvent.cs b/Viber.Api/Responses/GetWebHookEvent.cs deleted file mode 100644 index fe57c3f9..00000000 --- a/Viber.Api/Responses/GetWebHookEvent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Text.Json.Serialization; -using Viber.Api.Entities; - -namespace Viber.Api.Responses -{ - public class GetWebHookEvent - { - [JsonPropertyName("event")] - public string? Event { get; set; } - - [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } - - [JsonPropertyName("message_token")] - public long MessageToken { get; set; } - - [JsonPropertyName("user_id")] - public string? UserId { get; set; } - - [JsonPropertyName("desc")] - public string? Desc { get; set; } - - [JsonPropertyName("sender")] - public MessageSender? Sender { get; set; } - - [JsonPropertyName("message")] - public ViberMessage? Message { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Responses/SetWebHookResponse.cs b/Viber.Api/Responses/SetWebHookResponse.cs deleted file mode 100644 index dfd20f09..00000000 --- a/Viber.Api/Responses/SetWebHookResponse.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Viber.Api.Responses -{ - public class SetWebHookResponse - { - [JsonPropertyName("status")] - public int Status { get; set; } - - [JsonPropertyName("status_message")] - public string? StatusMessage { get; set; } - - [JsonPropertyName("event_types")] - public List? EventTypes { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Settings/ViberApiSettings.cs b/Viber.Api/Settings/ViberApiSettings.cs deleted file mode 100644 index 1b139c52..00000000 --- a/Viber.Api/Settings/ViberApiSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Viber.Api.Settings -{ - public class ViberApiSettings - { - public string? RemoteUrl { get; set; } - public string? HookUrl { get; set; } - public string? ViberToken { get; set; } - } -} \ No newline at end of file diff --git a/Viber.Api/Viber.Api.csproj b/Viber.Api/Viber.Api.csproj deleted file mode 100644 index d3ba5b9f..00000000 --- a/Viber.Api/Viber.Api.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - netstandard2.1 - enable - 0.7.0 - Botticelli - Igor Evdokimov - https://github.com/devgopher/botticelli - logo.jpg - https://github.com/devgopher/botticelli - - - - - - - - - - True - \ - - - - - - - - - - - - \ No newline at end of file diff --git a/Viber.Api/ViberService.cs b/Viber.Api/ViberService.cs deleted file mode 100644 index 0ec473e7..00000000 --- a/Viber.Api/ViberService.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Json; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Viber.Api.Exceptions; -using Viber.Api.Requests; -using Viber.Api.Responses; -using Viber.Api.Settings; - -namespace Viber.Api -{ - public class ViberService : IViberService - { - private readonly IHttpClientFactory _httpClientFactory; - private readonly HttpListener _httpListener; - private readonly ViberApiSettings _settings; - private readonly ManualResetEventSlim _stopReceiving = new ManualResetEventSlim(false); - - public ViberService(IHttpClientFactory httpClientFactory, - ViberApiSettings settings) - { - _httpListener = new HttpListener(); - _httpClientFactory = httpClientFactory; - _settings = settings; - _httpListener.Prefixes.Add("http://127.0.0.1:5000/"); - - Start(); - } - - public event IViberService.GotMessageHandler? GotMessage; - - public void Start() - { - _httpListener.Start(); - Task.Run(() => ReceiveMessages()); - - _ = SetWebHook(new SetWebHookRequest - { - Url = _settings.HookUrl, - AuthToken = _settings.ViberToken, - EventTypes = new List - { - "delivered", - "seen", - "failed", - "subscribed", - "unsubscribed", - "conversation_started" - } - }); - } - - public void Stop() - { - _stopReceiving.Set(); - _httpListener.Stop(); - } - - public async Task SetWebHook(SetWebHookRequest request, - CancellationToken cancellationToken = default) - { - request.AuthToken = _settings.ViberToken; - - return await InnerSend(request, - "set_webhook", - cancellationToken); - } - - public async Task SendMessage(ApiSendMessageRequest request, - CancellationToken cancellationToken = default) - { - request.AuthToken = _settings.ViberToken; - - return await InnerSend(request, - "send_message", - cancellationToken); - } - - public void Dispose() - { - Stop(); - _httpListener.Close(); - } - - protected async Task ReceiveMessages(CancellationToken cancellationToken = default) - { - while (!_stopReceiving.IsSet) - try - { - if (cancellationToken is {CanBeCanceled: true, IsCancellationRequested: true}) - { - _stopReceiving.Set(); - - return; - } - - if (!_httpListener.IsListening) continue; - - var context = await _httpListener.GetContextAsync(); - - if (context.Response.StatusCode == (int) HttpStatusCode.OK) - { - using var sr = new StreamReader(context.Response.OutputStream); - var content = await sr.ReadToEndAsync(); - var deserialized = JsonSerializer.Deserialize(content); - - if (deserialized == null) continue; - - GotMessage?.Invoke(deserialized); - } - else - { - throw new ViberClientException($"Error listening: {context.Response.StatusCode}, " + - $"{context.Response.StatusDescription}"); - } - } - catch (Exception? ex) - { - throw new ViberClientException(ex.Message, ex); - } - } - - private async Task InnerSend(TReq request, - string funcName, - CancellationToken cancellationToken) - { - using var httpClient = _httpClientFactory.CreateClient(); - httpClient.BaseAddress = new Uri( /*_settings.RemoteUrl*/ _settings.HookUrl); - - var content = JsonContent.Create(request); - - var httpResponse = await httpClient.PostAsync(funcName, content, cancellationToken); - - if (!httpResponse.IsSuccessStatusCode) throw new ViberClientException($"Error sending request {nameof(SetWebHook)}: {httpResponse.StatusCode}!"); - - if (httpResponse.Content == null) throw new ViberClientException(""); - - return await httpResponse.Content.ReadFromJsonAsync(cancellationToken); - } - } -} \ No newline at end of file