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