Compare commits

...
138 Commits
Author SHA1 Message Date
Florian Zevedei 98199f2e9b Adding additional intellisense documentation to refurbished Json serialization 2024-12-02 01:57:14 +01:00
Florian Zevedei 9146788ab0 Adding new legacy nuget package to recover old session serialization method 2024-12-02 01:56:32 +01:00
Florian Zevedei d72ee38349 Adding null checks to StopReceiving methods of clients 2024-11-18 02:47:20 +01:00
Florian Zevedei 667ca5ba6c Updating json serializer to fit System.Text.Json 2024-11-09 16:04:42 +01:00
Florian Zevedei 19d8a23b98 Adding IDeviceSession fixes to extension projects 2024-10-15 15:36:14 +02:00
Florian Zevedei db5dd7862c Adding IDeviceSession and IDeviceSessionMethods Interface 2024-10-15 15:32:32 +02:00
Florian Zevedei bc1f1fc93d Updating source generator to add method optional parameters 2024-10-15 15:28:44 +02:00
Florian Zevedei 4f23c85d16 Replace old framework versions in image library 2024-10-14 01:53:15 +02:00
Florian Zevedei b37b1e2a64 Preparation for version switch from 19 to 21 2024-10-14 01:45:46 +02:00
Florian Zevedei fb88d15485 Marking V19 GroupForm as obsolete for future updates
- and adding V21 version as well (as in alpha)
2024-10-14 01:43:42 +02:00
Florian Zevedei 06d4fda9a4 Removing old json serilization in test project 2024-10-14 01:42:46 +02:00
Florian Zevedei 0e8e96f2d8 Replacing Newtonsoft.Json with System.Text.Json (experimental) 2024-09-25 16:56:03 +02:00
Florian Zevedei f21dec5f1c Making xml property static 2024-07-15 23:50:56 +02:00
Florian Zevedei a1c4629351 Adding xml loader to source generator to add comments from nuget package 2024-07-15 18:51:30 +02:00
Florian Zevedei 9bdea52df9 Changing comments 2024-07-12 14:04:50 +02:00
Florian Zevedei 80eecabda4 Fixing incorrect formatting (indention) 2024-07-12 14:04:30 +02:00
Florian Zevedei 9f16cb4969 Adding source generator to add all API methods to the DeviceSession 2024-07-11 21:13:54 +02:00
Florian Zevedei eaa742e7df Adding .NET 8 to target frameworks 2024-07-11 21:13:14 +02:00
Florian Zevedei 7aa40b3e2d Using Message instead RawData.Message on session creation 2024-07-10 14:25:51 +02:00
Florian Zevedei eb8fad444b Fix for bots in groups adding/removing 2024-07-10 14:25:08 +02:00
Florian Zevedei 1f1aa1f0ee Adding throwPendingUpdates to QuickStart in BBB 2024-07-03 15:56:50 +02:00
Florian Zevedei 39e68a2089 Merge branch 'master' into development 2024-06-29 18:28:37 +02:00
Florian Zevedei 312690c002 Fix builder in sample 2024-06-25 13:43:33 +02:00
Florian Zevedei 99b888a7a2 Replacing project reference with nuget package in samples 2024-06-25 13:43:21 +02:00
Florian Zevedei 6adcc52ea2 Adding null/empty checks to some controls to prevent invalid behaviour 2024-06-09 14:19:04 +02:00
Florian Zevedei 834038ff44 Adding additional bot command checks toa void invalid configurations 2024-06-09 14:13:43 +02:00
Florian Zevedei e3d1652a02 Fixing on EditableMessage 2024-05-29 21:35:09 +02:00
Florian Zevedei a7411176e6 Fixing the prototype solution of group commands detection 2024-05-29 21:29:14 +02:00
Florian Zevedei 801c0b77f8 Introducing BotGroupCommand property. 2024-05-27 21:39:36 +02:00
Florian Zevedei 7a86ecdf32 Breakable change! Remove of most try/catch's in DeviceSession 2024-05-25 17:24:24 +02:00
Florian Zevedei 175927fbab Updating nuget dependencies 2 2024-05-19 14:16:18 +02:00
Florian Zevedei 34e4429b38 Updating nuget dependencies 2024-05-19 14:15:56 +02:00
Florian Zevedei dc28bde382 Fix Action method handling, if handled by control already 2024-05-19 14:06:01 +02:00
Florian Zevedei 87d57c471a Update CallbackDataTooLongException.cs 2024-05-12 19:07:33 +03:00
Florian Zevedei 5ce2360cc7 Adding callbackdata too long exception and checks 2024-05-12 19:01:50 +03:00
Florian Zevedei e345fc2948 Fixing message loop handling of Action methods 2024-05-12 18:45:18 +03:00
Florian Zevedei 652526ec59 Consolidate nuget references 2024-05-12 18:40:55 +03:00
Florian Zevedei b829f43c5d Adding a exception for invalid ServiceProviders #62
- adding an internal exception handler to catch InvalidOperationException and throw a new explicit one
2024-03-10 17:09:13 +01:00
Florian Zevedei 6e7acdbdba Bugfix in BBB 2024-03-10 16:49:18 +01:00
Florian Zevedei 396a524d94 BotBaseBuilder fix 2024-03-10 16:01:45 +01:00
Florian Zevedei 7d679649e4 Merge pull request #64 from MajMcCloud/master
Update development branch
2024-03-10 15:54:29 +01:00
Florian Zevedei 2db8e2cf63 Merge pull request #63 from MajMcCloud/development
Integrating latest developments into master branch
2024-03-10 15:51:29 +01:00
Florian Zevedei 6b7f2ca273 Merge branch 'master' into development 2024-03-10 15:50:42 +01:00
Florian Zevedei 31d0d9890d Merge pull request #61 from ramax495/RuLocalization
Fix typo
2024-01-31 14:15:44 +01:00
ramax495 b252829296 Fix typo 2024-01-31 10:01:13 +03:00
Florian Zevedei a483bc7c0b Update Russian.cs 2024-01-30 21:57:05 +01:00
Florian Zevedei 4635a96fc7 Removing old comments 2024-01-30 19:24:13 +01:00
Florian Zevedei 727375f7aa Localization fixes 2024-01-30 19:15:11 +01:00
Florian Zevedei 86792af214 Fixing incorrect behaviour with tags 2024-01-30 18:27:48 +01:00
Florian Zevedei b3edb95b55 Adding new total tag and checked tag count 2024-01-30 18:27:05 +01:00
Florian Zevedei 946420f9c6 Fixing InlineKeyboard use
- tags cant be change when using InlineKeyboard mode
2024-01-30 18:26:40 +01:00
Florian Zevedei c8c56a42c0 Bugfix for TaggedButtonGrid 2024-01-30 18:24:31 +01:00
Florian Zevedei 6f930eee8c Adding new constructor and additonal ToRowList method to ButtonForm 2024-01-30 18:23:29 +01:00
Florian Zevedei c4a589d476 Renaming TaggedButtonGrid specific localizations 2024-01-30 18:23:02 +01:00
Florian Zevedei 1b14b7b3cf Adding missing localization to german file 2024-01-30 18:21:25 +01:00
Florian Zevedei cb5fa35269 Fix #60 2024-01-30 16:07:08 +01:00
Florian Zevedei f60e1000c3 Changing datatype of port from string to int 2024-01-30 15:08:40 +01:00
Florian Zevedei 00dafd59a3 Update README.md 2024-01-30 14:47:52 +01:00
Florian Zevedei 848b5d29cf Add missing Russian localization due to PR #58 2024-01-30 14:47:23 +01:00
Florian Zevedei 9cac57b8e7 Update README.md 2024-01-28 19:32:26 +01:00
Florian Zevedei 52517534f8 Update README.md 2024-01-28 19:30:17 +01:00
Florian Zevedei 5e166db243 Adding readme note for IronSoftware extension 2024-01-28 19:25:59 +01:00
Florian Zevedei 71aa0c3d91 Adding nuget references and nuget specs 2024-01-28 19:24:32 +01:00
Florian Zevedei e34c71b42b Update README.md 2024-01-28 19:23:50 +01:00
Florian Zevedei 9927a034b0 First release of IronSoftware drawing extension 2024-01-28 19:22:30 +01:00
Florian Zevedei 2e454d7f5c Removing SQLClient dependency 2024-01-28 19:17:15 +01:00
Florian Zevedei 339319ef2a Update README.md 2024-01-28 18:50:27 +01:00
Florian Zevedei c8b12e9168 Adding credits 2024-01-28 18:49:52 +01:00
Florian Zevedei cbc31b8ff5 adding source path to Readme 2024-01-28 18:48:31 +01:00
Florian Zevedei c514ede03f Adding PostgreSQL extension to Readme 2024-01-28 18:45:17 +01:00
Florian Zevedei c91fbaa16c Downgrade target framework to.NET 6 2024-01-28 18:39:56 +01:00
Florian Zevedei b8125e9aed Merge pull request #58 from Kataane/add-postgresql-serializer
Add PostgreSQL serializer
2024-01-28 18:34:15 +01:00
Florian Zevedei d4af8797fb Add new PostgreSQL extension to solution file 2024-01-28 02:16:55 +01:00
Florian Zevedei 93fda551e1 Merge pull request #57 from Kataane/master
Russian translation added
2024-01-28 02:04:13 +01:00
Kataane b14913362e Update README.md 2024-01-27 13:21:46 +07:00
Kataanee 8b22fd7b74 Fast fix 2024-01-27 13:16:27 +07:00
Kataanee 254c002d4a Add Russian in ILanguageSelectionStage 2024-01-27 13:14:15 +07:00
Kataanee a632440efd Add PostgreSQL serializer 2024-01-26 18:48:43 +07:00
Kataanee 194d8ba317 Revert "Russian translation added"
This reverts commit d833b1e088.
2024-01-26 18:46:49 +07:00
Kataanee d833b1e088 Russian translation added 2024-01-26 12:42:41 +07:00
Florian Zevedei 084395e962 Bugfix, adding missing DeviceSession to UpdateResult 2024-01-24 22:15:16 +01:00
Florian Zevedei b20b3b2826 Bugfix, adding missing DeviceSession to UpdateResult 2024-01-24 22:12:26 +01:00
Florian Zevedei c1d1d5f543 Fix, add missing ThrowPendingUpdates 2024-01-24 17:48:25 +01:00
Florian Zevedei 078bdfb163 Making cancellationTokenSource protected 2024-01-24 17:43:44 +01:00
Florian Zevedei 94c680e036 Timeout fixes
- Fixing wrong timeout setter
- removing timeout setter in "Prepare" method in client which overrides setting
2024-01-24 17:39:18 +01:00
Florian Zevedei dbdc40582a Splitting MessageClient into 2 instances 2024-01-24 03:04:05 +01:00
Florian Zevedei 2d3393aa05 Update README.md 2024-01-17 22:55:11 +01:00
Florian Zevedei fdd814c88e Adding project reference links 2024-01-17 22:54:30 +01:00
Florian Zevedei 2b06fba8ff Adding readme note for IronSoftware extension 2024-01-17 22:51:40 +01:00
Florian Zevedei 8e4bc7a222 Bumps Microsoft.Data.SqlClient from 5.0.0 to 5.1.3. 2024-01-17 22:42:03 +01:00
Florian Zevedei cc4c743721 Merge pull request #56 from MajMcCloud/dependabot/nuget/TelegramBotBase.Extensions.Serializer.Database.MSSQL/Microsoft.Data.SqlClient-5.1.3
Bump Microsoft.Data.SqlClient from 5.0.0 to 5.1.3 in /TelegramBotBase.Extensions.Serializer.Database.MSSQL
2024-01-17 22:41:15 +01:00
dependabot[bot] 4e55fd42d6 Bump Microsoft.Data.SqlClient
Bumps [Microsoft.Data.SqlClient](https://github.com/dotnet/sqlclient) from 5.0.0 to 5.1.3.
- [Release notes](https://github.com/dotnet/sqlclient/releases)
- [Changelog](https://github.com/dotnet/SqlClient/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotnet/sqlclient/compare/v5.0.0...v5.1.3)

---
updated-dependencies:
- dependency-name: Microsoft.Data.SqlClient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-16 19:28:47 +00:00
Florian Zevedei deef8dd086 Replacing project references with nuget reference 2024-01-04 14:17:09 +01:00
Florian Zevedei 3aff0aa94b Updating example projects for use of SingleThread/ThreadPool 2024-01-04 14:16:52 +01:00
Florian Zevedei 75064fd64c Adding nuget references and nuget specs 2024-01-04 14:11:21 +01:00
Florian Zevedei 0cafca9aee Update README.md 2024-01-04 13:46:58 +01:00
Florian Zevedei ad40675b87 First release of IronSoftware drawing extension 2024-01-04 13:44:18 +01:00
Florian Zevedei 83e09aec2a Removing System.Drawing.Common dependency 2023-12-29 14:38:11 +01:00
Florian Zevedei 26152ee348 Removing System.Drawing.Common dependency 2023-12-29 14:37:19 +01:00
Florian Zevedei d5ffa914f0 Update README.md 2023-12-28 15:17:43 +01:00
Florian Zevedei a63f71268c Update README.md 2023-12-28 15:17:14 +01:00
Florian Zevedei 7fcaa407ad Adding parallel processing via ThreadPool 2023-12-26 17:40:13 +01:00
Florian Zevedei d56b26a788 Merge pull request #55 from MajMcCloud/development
Integrating development branch into master
2023-12-26 17:32:51 +01:00
Florian Zevedei c1018ac5a0 Adding ThrowPendingUpdates option to builder and Message client. 2023-12-09 14:56:02 +01:00
Florian Zevedei 064399ed77 Replace nuget reference with project for development 2023-12-09 14:54:23 +01:00
Florian Zevedei 121a788c17 Update README.md 2023-12-08 01:52:47 +01:00
Florian Zevedei 40199a1d81 Merge pull request #51 from contributeless/feature/ability-to-log-exceptions-to-any-destination
Added the ReceiveError event to MessageClient to provide the ability to handle exceptions in client code
2023-12-07 17:50:57 +01:00
Florian Zevedei 794dee7f58 improved readability 2023-12-07 17:49:35 +01:00
Florian Zevedei 88309e607b Adding localization notes to Readme file 2023-12-06 15:44:59 +01:00
Florian Zevedei a71eed5054 Merge pull request #54 from MajMcCloud/master
Syncing master and development branch
2023-12-06 15:36:53 +01:00
Florian Zevedei 2a873cc29b Merge pull request #53 from MajMcCloud/development
Integrating development branch into master
2023-12-06 15:23:47 +01:00
Florian Zevedei f401216eff Fixing EditMessage behaviour 2023-12-06 15:19:28 +01:00
Florian Zevedei 0cb4f02a8d Merge pull request #52 from contributeless/fix/get-device-id-from-edited-message
Fix: get chat id from edited message
2023-12-06 15:17:17 +01:00
TCSBANK\e.khudik 62b6bafbd3 Fix: get chat id from edited message 2023-12-05 13:02:53 +03:00
TCSBANK\e.khudik 89332244e3 Added the ReceiveError event to MessageClient to provide the ability to handle exceptions in client code 2023-12-04 15:10:28 +03:00
Florian Zevedei 43f4ab1061 Update README.md 2023-11-26 15:09:42 +01:00
Florian Zevedei 1f438186f4 Merge pull request #50 from MajMcCloud/master
Merging master changes into development branch
2023-11-26 15:08:56 +01:00
Florian Zevedei f447b058d0 Update README.md 2023-11-26 15:08:40 +01:00
Florian Zevedei 40ba9bd8ab Update README.md 2023-11-26 15:07:43 +01:00
Florian Zevedei ca473f78ed Adding /start command handling to test project 2023-11-02 14:52:05 +01:00
Florian Zevedei ab702dbc9f Added missing UsePersian method to interface. 2023-11-01 01:09:35 +01:00
Florian Zevedei 23dc1ff1d6 Merge pull request #48 from mottaghipour/master
[LANGUAGE] implement Persian language class
2023-11-01 01:01:25 +01:00
AmirAbbas e044d725c9 [FEATURE] add UsePersian method to BotBaseBuilder class 2023-10-31 13:25:46 +03:30
AmirAbbas 4825beea7e [FEATURE] implement Persian language class 2023-10-31 13:23:41 +03:30
AmirAbbas 78d65868ff [BASE] Init Persian language class 2023-10-31 13:11:02 +03:30
AmirAbbas d7aca80d59 [REFACTOR] convert language classes to language sealed classes 2023-10-31 13:10:31 +03:30
Florian Zevedei 73863c4154 Adding project files 2023-09-29 18:40:56 +02:00
Florian Zevedei e3b1a32535 Adding missing Program.cs 2023-09-29 18:40:20 +02:00
Florian Zevedei 571049eeb4 Replacing exception text with nameof litteral 2023-09-29 18:34:33 +02:00
Florian Zevedei 30222d640d Adding full DI navigation example for testing purposes.
- adding internal private field into FormBase class
- adding Extension methods into DependencyInjection namespace for use
- adding 2 NavigateTo extension methods for DI using ServiceProvider
- adding example project
- adding to readme as example
2023-09-29 18:34:10 +02:00
Florian Zevedei e6a48115a6 Adding xml comments to BotBase 2023-09-29 18:28:55 +02:00
Florian Zevedei b278015dd4 Replacing static names in exception messages with nameof literal 2023-09-29 18:26:39 +02:00
Florian Zevedei 91e0c54728 Merge pull request #46 from MajMcCloud/development
Migrate development to master
2023-09-16 17:22:01 +02:00
Florian Zevedei 788f935c02 Add latest nuget package instead of local project to extensions 2023-09-16 17:11:40 +02:00
Florian Zevedei d1089fe716 editing Label example project 2023-09-16 17:11:21 +02:00
Florian Zevedei 1d3db0116e Update README.md 2023-09-15 18:52:15 +02:00
Florian Zevedei e95301f727 Update README.md 2023-09-15 18:50:16 +02:00
Florian Zevedei 4ba91803fe Update README.md 2023-09-13 15:13:22 +02:00
97 changed files with 18557 additions and 540 deletions
@@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj"/> <PackageReference Include="TelegramBotBase" Version="6.4.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="TelegramBotBase" Version="5.3.0" /> <PackageReference Include="TelegramBotBase" Version="6.4.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -16,6 +16,7 @@ namespace BotAndWebApplication.BotStuff.Tasks
.DefaultCommands() .DefaultCommands()
.NoSerialization() .NoSerialization()
.UseEnglish() .UseEnglish()
.UseThreadPool()
.Build(); .Build();
} }
@@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore;
namespace DependencyInjection.Database;
public class BotDbContext : DbContext
{
public BotDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
@@ -0,0 +1,7 @@
namespace DependencyInjection.Database;
public class User
{
public long Id { get; set; }
public string LastMessage { get; set; }
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,67 @@
using DependencyInjection.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.DependencyInjection;
namespace DependencyInjection.Forms
{
public class ConfirmationForm : FormBase
{
private readonly BotDbContext _dbContext;
public ConfirmationForm(BotDbContext dbContext)
{
_dbContext = dbContext;
}
public override async Task Load(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
{
await this.NavigateTo<StartForm>();
return;
}
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("Go back");
switch (message.RawData)
{
case "back":
await this.NavigateTo<StartForm>();
break;
}
}
public override async Task Render(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Back", "back");
await Device.Send($"ConfirmationForm: Your last message was: {user.LastMessage}. Click \"Back\" to get back.", bf);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using DependencyInjection.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TelegramBotBase.Builder;
namespace DependencyInjection
{
internal class Program
{
static async Task Main(string[] args)
{
var serviceCollection = new ServiceCollection()
.AddDbContext<BotDbContext>(x => x.UseInMemoryDatabase("TelegramBotBase"));
var serviceProvider = serviceCollection.BuildServiceProvider();
var bot = BotBaseBuilder.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ??
throw new Exception("API_KEY is not set"))
.DefaultMessageLoop()
.WithServiceProvider<StartForm>(serviceProvider)
.NoProxy()
.NoCommands()
.NoSerialization()
.DefaultLanguage()
.UseSingleThread()
.Build();
await bot.Start();
await Task.Delay(-1);
}
}
}
+79
View File
@@ -0,0 +1,79 @@
using DependencyInjection.Database;
using DependencyInjection.Forms;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.DependencyInjection;
namespace DependencyInjection;
public class StartForm : FormBase
{
private readonly BotDbContext _dbContext;
public StartForm(BotDbContext dbContext)
{
_dbContext = dbContext;
}
public override async Task Load(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user is null)
{
user = new User
{
Id = Device.DeviceId,
LastMessage = "<unknown>"
};
_dbContext.Users.Add(user);
await _dbContext.SaveChangesAsync();
}
if (message.IsAction)
return;
user.LastMessage = string.IsNullOrWhiteSpace(message.MessageText) ? "<unknown>" : message.MessageText;
await _dbContext.SaveChangesAsync();
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("Ok");
switch(message.RawData)
{
case "open":
await this.NavigateTo(typeof(ConfirmationForm));
var new_form = await this.NavigateTo<ConfirmationForm>();
if (new_form == null)
{
await Device.Send("Cant open ConfirmationForm");
}
break;
}
}
public override async Task Render(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Open confirmation", "open");
await Device.Send($"Your last message's text was: `{user.LastMessage}`", bf);
}
}
+3 -3
View File
@@ -8,12 +8,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9"/> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.9"/> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.11" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj"/> <ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+1
View File
@@ -18,6 +18,7 @@ var bot = BotBaseBuilder.Create()
.NoCommands() .NoCommands()
.NoSerialization() .NoSerialization()
.DefaultLanguage() .DefaultLanguage()
.UseSingleThread()
.Build(); .Build();
await bot.Start(); await bot.Start();
@@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj" /> <PackageReference Include="TelegramBotBase" Version="6.4.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -21,12 +21,15 @@ namespace InlineAndReplyCombination
.DefaultCommands() .DefaultCommands()
.UseJSON(Path.Combine(Directory.GetCurrentDirectory(), "states.json")) .UseJSON(Path.Combine(Directory.GetCurrentDirectory(), "states.json"))
.UseEnglish() .UseEnglish()
.UseSingleThread()
.Build(); .Build();
await BotBaseInstance.UploadBotCommands(); await BotBaseInstance.UploadBotCommands();
BotBaseInstance.BotCommand += BotBaseInstance_BotCommand;
await BotBaseInstance.Start(); await BotBaseInstance.Start();
@@ -37,5 +40,28 @@ namespace InlineAndReplyCombination
await BotBaseInstance.Stop(); await BotBaseInstance.Stop();
} }
private static async Task BotBaseInstance_BotCommand(object sender, TelegramBotBase.Args.BotCommandEventArgs e)
{
switch(e.Command)
{
case "/start":
var start = new StartForm();
await e.Device.ActiveForm.NavigateTo(start);
break;
}
}
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj"/> <PackageReference Include="TelegramBotBase" Version="6.4.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+86 -5
View File
@@ -10,12 +10,14 @@
**Support group: [@tgbotbase](https://t.me/tgbotbase)** **Support group: [@tgbotbase](https://t.me/tgbotbase)**
**Discord Server: [https://discord.gg/p3PPhYbTf](https://discord.gg/p3PPhYbTf)** **Discord Server: [https://discord.gg/V3PxreDYfE](https://discord.gg/V3PxreDYfE)**
**Releases: [GitHub](https://github.com/MajMcCloud/TelegramBotFramework/releases)** **Releases: [GitHub](https://github.com/MajMcCloud/TelegramBotFramework/releases)**
## Donate ## Donate
Paypal: [https://paypal.me/majmccloud](https://paypal.me/majmccloud)
Bitcoin: `1GoUJYMwAvBipQTfw2FKydAz12J8RDyeJs` / `bc1qqwlp0p5ley29lsu6jhe0qv7s7963kfc7d0m53d` Bitcoin: `1GoUJYMwAvBipQTfw2FKydAz12J8RDyeJs` / `bc1qqwlp0p5ley29lsu6jhe0qv7s7963kfc7d0m53d`
Etherium: `0xAf3835104c2C3E5b3e721FA2c7365955e87DB931` Etherium: `0xAf3835104c2C3E5b3e721FA2c7365955e87DB931`
@@ -57,6 +59,7 @@ BitTorrent: `TYVZSykaVT1nKZnz9hjDgBRNB9VavU1bpW`
* [TaggedButtonGrid](#tagged-button-grid) * [TaggedButtonGrid](#tagged-button-grid)
* [CheckedButtonList](#checked-button-list) * [CheckedButtonList](#checked-button-list)
* [MultiToggleButton](#multi-toggle-button) * [MultiToggleButton](#multi-toggle-button)
- [Localizations](#localizations)
- [Groups](#groups) - [Groups](#groups)
* [SplitterForm](#splitter-form) * [SplitterForm](#splitter-form)
* [GroupForm](#group-form) * [GroupForm](#group-form)
@@ -641,7 +644,7 @@ await this.NavigateTo(cd);
A minimal control which allows to manage a classic "text" message and update its later without having to keep track of the message id. A minimal control which allows to manage a classic "text" message and update its later without having to keep track of the message id.
<img src=".github/images/label.gif?raw=true" /> <img src=".github/images/label.gif?raw=true" style="width:500px" />
Check the example project [TelegramBotBase.Test/Tests/Controls/LabelForm.cs](TelegramBotBase.Test/Tests/Controls/LabelForm.cs) Check the example project [TelegramBotBase.Test/Tests/Controls/LabelForm.cs](TelegramBotBase.Test/Tests/Controls/LabelForm.cs)
@@ -712,6 +715,20 @@ Check the example project [TelegramBotBase.Test/Tests/Controls/CheckedButtonList
Check the example project [TelegramBotBase.Test/Tests/Controls/MultiToggleButtonForm.cs](TelegramBotBase.Test/Tests/Controls/MultiToggleButtonForm.cs) Check the example project [TelegramBotBase.Test/Tests/Controls/MultiToggleButtonForm.cs](TelegramBotBase.Test/Tests/Controls/MultiToggleButtonForm.cs)
## Localizations
The current available languages for controls are:
- English
- German
- Persian
- Russian
You can add other languages easily by creating a subclass of the [TelegramBotBase/Localizations/Localization.cs](TelegramBotBase/Localizations/Localization.cs) class.
To set the default language set the *Language* property on the static [TelegramBotBase/Localizations/Default.cs](TelegramBotBase/Localizations/Default.cs) instance.
## Groups ## Groups
For groups, there are multiple different tools which help to work with and allows bot also to manage For groups, there are multiple different tools which help to work with and allows bot also to manage
@@ -1023,6 +1040,8 @@ again at 1 (due to `PopAsync` or `PopToRootAsync` calls) it will replace the con
you you
have given to the constructor at the beginning.* have given to the constructor at the beginning.*
---
## Extensions ## Extensions
### TelegramBotBase.Extensions.Images ### TelegramBotBase.Extensions.Images
@@ -1032,7 +1051,43 @@ Extends the base package with some additional image methods like SendPhoto (usin
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/) [![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/)
[![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images) [![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images)
[https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/) Source code: [TelegramBotBase.Extensions.Images/](/TelegramBotBase.Extensions.Images/)
Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/)
---
### TelegramBotBase.Extensions.Images.IronSoftware
Extends the base package with some additional image methods like SendPhoto (using Bitmap)
Important: This extension uses the IronSoftware drawing library.
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
[![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware)
[https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
Source code: [TelegramBotBase.Extensions.Images.IronSoftware/](TelegramBotBase.Extensions.Images.IronSoftware/)
Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.IronSoftware/](https://www.nuget.org/packages/TelegramBotBase.Extensions.IronSoftware/)
---
Project: [open source](TelegramBotBase.Extensions.Images/)
### TelegramBotBase.Extensions.Images.IronSoftware
Extends the base package with some additional image methods like SendPhoto (using Bitmap)
Important: This extension uses the IronSoftware drawing library.
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
[![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware)
[https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
Project: [open source](TelegramBotBase.Extensions.Images.IronSoftware/)
### TelegramBotBase.Extensions.Serializer.Database.MSSQL ### TelegramBotBase.Extensions.Serializer.Database.MSSQL
@@ -1041,7 +1096,28 @@ A session serializer for Microsoft SQL Server.
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Serializer.Database.MSSQL.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/) [![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Serializer.Database.MSSQL.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
[![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Serializer.Database.MSSQL.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL) [![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Serializer.Database.MSSQL.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL)
[https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/) Source code: [TelegramBotBase.Extensions.Serializer.Database.MSSQL/](/TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
---
### TelegramBotBase.Extensions.Serializer.Database.PostgreSql
A session serializer for PostgreSql Server.
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/)
[![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql)
Source code: [TelegramBotBase.Extensions.Serializer.Database.PostgreSql/](/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/)
Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/)
Credits: [@Kataane](https://github.com/Kataane)
---
Project: [open source](TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
## Test Project ## Test Project
@@ -1080,4 +1156,9 @@ Having already a web application and want to add a TelegramBot side-by-side with
Want to use Inline- and ReplyMarkup at the same time ? Here is an example how you can do that: Want to use Inline- and ReplyMarkup at the same time ? Here is an example how you can do that:
- [Examples/InlineAndReplyCombination](Examples/InlineAndReplyCombination) - [Examples/InlineAndReplyCombination](Examples/InlineAndReplyCombination)
Alpha: Full Dependency Injection example within this framework.
- [Examples/DependencyInjection](Examples/DependencyInjection)
@@ -0,0 +1,74 @@
using IronSoftware.Drawing;
using SixLabors.ImageSharp;
using System.IO;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
using static IronSoftware.Drawing.AnyBitmap;
using SKImage = SixLabors.ImageSharp.Image;
namespace TelegramBotBase.Extensions.Images.IronSoftware
{
public static class ImageExtensions
{
public static Stream ToStream(this AnyBitmap image, ImageFormat format)
{
var stream = new MemoryStream();
image.ExportStream(stream, format);
stream.Position = 0;
return stream;
}
public static async Task<Stream> ToStream(this SKImage image)
{
var stream = new MemoryStream();
await image.SaveAsPngAsync(stream);
stream.Position = 0;
return stream;
}
/// <summary>
/// Sends an image
/// </summary>
/// <param name="image"></param>
/// <param name="name"></param>
/// <param name="buttons"></param>
/// <param name="replyTo"></param>
/// <param name="disableNotification"></param>
/// <returns></returns>
public static async Task<Message> SendPhoto(this IDeviceSession session, AnyBitmap image, string name,
string caption, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false)
{
using (var fileStream = ToStream(image, ImageFormat.Png))
{
var fts = InputFile.FromStream(fileStream, name);
return await session.SendPhoto(fts, caption, buttons, replyTo, disableNotification);
}
}
/// <summary>
/// Sends an image
/// </summary>
/// <param name="image"></param>
/// <param name="name"></param>
/// <param name="buttons"></param>
/// <param name="replyTo"></param>
/// <param name="disableNotification"></param>
/// <returns></returns>
public static async Task<Message> SendPhoto(this IDeviceSession session, SKImage image, string name,
string caption, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false)
{
using (var fileStream = await ToStream(image))
{
var fts = InputFile.FromStream(fileStream, name);
return await session.SendPhoto(fts, caption, buttons, replyTo, disableNotification);
}
}
}
}
@@ -0,0 +1,12 @@
# TelegramBotBase.Extensions.Images.IronSoftware
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
[![Telegram chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://www.t.me/tgbotbase)
[![License](https://img.shields.io/github/license/MajMcCloud/telegrambotframework.svg?style=flat-square&maxAge=2592000&label=License)](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
[![Package Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware)
This extension uses the [IronSoftware](https://ironsoftware.com/open-source/csharp/drawing/docs/) drawing library.
Check [https://ironsoftware.com/open-source/csharp/drawing/docs/](https://ironsoftware.com/open-source/csharp/drawing/docs/) for more details on this library.
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net6</TargetFrameworks>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images.IronSoftware</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images.IronSoftware</PackageProjectUrl>
<Copyright>MIT</Copyright>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.1</FileVersion>
<Description>This is an extension for sending Bitmap/Images platform independent by using IronSoftware's drawing library via TelegramBotBase.</Description>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IronSoftware.System.Drawing" Version="2024.5.1" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
</Project>
@@ -4,7 +4,7 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using TelegramBotBase.Form; using TelegramBotBase.Form;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Extensions.Images namespace TelegramBotBase.Extensions.Images
{ {
@@ -27,7 +27,7 @@ namespace TelegramBotBase.Extensions.Images
/// <param name="replyTo"></param> /// <param name="replyTo"></param>
/// <param name="disableNotification"></param> /// <param name="disableNotification"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<Message> SendPhoto(this DeviceSession session, Image image, string name, public static async Task<Message> SendPhoto(this IDeviceSession session, Image image, string name,
string caption, ButtonForm buttons = null, int replyTo = 0, string caption, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false) bool disableNotification = false)
{ {
@@ -48,7 +48,7 @@ namespace TelegramBotBase.Extensions.Images
/// <param name="replyTo"></param> /// <param name="replyTo"></param>
/// <param name="disableNotification"></param> /// <param name="disableNotification"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<Message> SendPhoto(this DeviceSession session, Bitmap image, string name, public static async Task<Message> SendPhoto(this IDeviceSession session, Bitmap image, string name,
string caption, ButtonForm buttons = null, int replyTo = 0, string caption, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false) bool disableNotification = false)
{ {
@@ -1,12 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6</TargetFrameworks> <TargetFrameworks>netcoreapp3.1;net6;net7;net8</TargetFrameworks>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl> <RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl> <PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images</PackageProjectUrl>
<Copyright>MIT</Copyright> <Copyright>MIT</Copyright>
<IncludeSymbols>true</IncludeSymbols> <IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<Authors>$(AssemblyName)</Authors>
<Description>This is an extension for sending Bitmap/Images via TelegramBotBase.</Description>
<FileVersion>1.1.1</FileVersion>
<AssemblyVersion>1.1.1</AssemblyVersion>
<Version>1.1.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -17,12 +23,12 @@
<PackageReference Include="System.Drawing.Common" Version="6.0.0" /> <PackageReference Include="System.Drawing.Common" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Properties\" /> <Folder Include="Properties\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
</Project> </Project>
@@ -31,7 +31,7 @@ namespace TelegramBotBase.Extensions.Serializer.Database.MSSQL
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase))) if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{ {
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase"); throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
} }
} }
@@ -16,15 +16,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.0.0"/> <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1"> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
</ItemGroup> <PackageReference Include="TelegramBotBase" Version="6.0.0" />
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,92 @@
using System;
using TelegramBotBase.Builder;
using TelegramBotBase.Builder.Interfaces;
namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
{
/// <summary>
/// Provides extension methods for configuring the use of PostgreSQL Server Database for session serialization.
/// </summary>
public static class BotBaseBuilderExtensions
{
/// <summary>
/// Uses an PostgreSQL Server Database to save and restore sessions.
/// </summary>
/// <param name="builder">The session serialization stage builder.</param>
/// <param name="connectionString">The connection string to the PostgreSQL database.</param>
/// <param name="fallbackForm">The fallback form type.</param>
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
/// <returns>The language selection stage builder.</returns>
public static ILanguageSelectionStage UsePostgreSqlDatabase(
this ISessionSerializationStage builder,
string connectionString, Type fallbackForm = null,
string tablePrefix = "tgb_")
{
var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
builder.UseSerialization(serializer);
return builder as BotBaseBuilder;
}
/// <summary>
/// Uses an PostgreSQL Server Database to save and restore sessions.
/// </summary>
/// <param name="builder">The session serialization stage builder.</param>
/// <param name="hostOrIp">The host or IP address of the PostgreSQL server.</param>
/// <param name="port">The port number for the PostgreSQL server.</param>
/// <param name="databaseName">The name of the PostgreSQL database.</param>
/// <param name="userId">The user ID for connecting to the PostgreSQL server.</param>
/// <param name="password">The password for connecting to the PostgreSQL server.</param>
/// <param name="fallbackForm">The fallback form type.</param>
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
/// <returns>The language selection stage builder.</returns>
public static ILanguageSelectionStage UsePostgreSqlDatabase(
this ISessionSerializationStage builder,
string hostOrIp, int port,
string databaseName, string userId,
string password, Type fallbackForm = null,
string tablePrefix = "tgb_")
{
var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Username={userId};Password={password}";
var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
builder.UseSerialization(serializer);
return builder as BotBaseBuilder;
}
/// <summary>
/// Uses an PostgreSQL Server Database with Windows Authentication to save and restore sessions.
/// </summary>
/// <param name="builder">The session serialization stage builder.</param>
/// <param name="hostOrIp">The host or IP address of the PostgreSQL server.</param>
/// <param name="port">The port number for the PostgreSQL server.</param>
/// <param name="databaseName">The name of the PostgreSQL database.</param>
/// <param name="integratedSecurity">A flag indicating whether to use Windows Authentication (true) or not (false).</param>
/// <param name="fallbackForm">The fallback form type.</param>
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
/// <returns>The language selection stage builder.</returns>
public static ILanguageSelectionStage UsePostgreSqlDatabase(
this ISessionSerializationStage builder,
string hostOrIp, int port,
string databaseName, bool integratedSecurity = true,
Type fallbackForm = null, string tablePrefix = "tgb_")
{
if (!integratedSecurity)
{
throw new ArgumentOutOfRangeException();
}
var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Integrated Security=true;";
var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
builder.UseSerialization(serializer);
return builder as BotBaseBuilder;
}
}
}
@@ -0,0 +1,252 @@
using Npgsql;
using System;
using System.Data;
using NpgsqlTypes;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
{
/// <summary>
/// Represents a PostgreSQL implementation of the <see cref="IStateMachine"/> for saving and loading form states.
/// </summary>
public class PostgreSqlSerializer : IStateMachine
{
private readonly string insertIntoSessionSql;
private readonly string insertIntoSessionsDataSql;
private readonly string selectAllDevicesSessionsSql;
private readonly string selectAllDevicesSessionsDataSql;
/// <summary>
/// Initializes a new instance of the <see cref="PostgreSqlSerializer"/> class.
/// </summary>
/// <param name="connectionString">The connection string to the PostgreSQL database.</param>
/// <param name="tablePrefix">The prefix for database table names (default is "tgb_").</param>
/// <param name="fallbackStateForm">The fallback state form type.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="connectionString"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="fallbackStateForm"/> is not a subclass of <see cref="FormBase"/>.</exception>
public PostgreSqlSerializer(string connectionString, string tablePrefix = "tgb_", Type fallbackStateForm = null)
{
ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
TablePrefix = tablePrefix;
FallbackStateForm = fallbackStateForm;
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
insertIntoSessionSql = "INSERT INTO " + TablePrefix +
"devices_sessions (deviceId, deviceTitle, \"FormUri\", \"QualifiedName\") VALUES (@deviceId, @deviceTitle, @FormUri, @QualifiedName)";
insertIntoSessionsDataSql = "INSERT INTO " + TablePrefix + "devices_sessions_data (deviceId, key, value, type) VALUES (@deviceId, @key, @value, @type)";
selectAllDevicesSessionsSql = "SELECT * FROM " + TablePrefix + "devices_sessions";
selectAllDevicesSessionsDataSql = "SELECT * FROM " + TablePrefix + "devices_sessions_data WHERE deviceId = @deviceId";
}
/// <summary>
/// Gets the connection string to the PostgreSQL database.
/// </summary>
public string ConnectionString { get; }
/// <summary>
/// Gets or sets the table name prefix for database tables.
/// </summary>
public string TablePrefix { get; set; }
/// <summary>
/// Gets or sets the fallback state form type.
/// </summary>
public Type FallbackStateForm { get; set; }
/// <inheritdoc/>
/// <summary>
/// Saves form states to the PostgreSQL database.
/// </summary>
/// <param name="e">The <see cref="SaveStatesEventArgs"/> containing the states to be saved.</param>
public void SaveFormStates(SaveStatesEventArgs e)
{
var container = e.States;
//Cleanup old Session data
Cleanup();
using (var connection = new NpgsqlConnection(ConnectionString))
{
connection.Open();
//Store session data in database
foreach (var state in container.States)
{
using (var sessionCommand = connection.CreateCommand())
{
sessionCommand.CommandText = insertIntoSessionSql;
sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint){Value = state.DeviceId });
sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceTitle", DbType.StringFixedLength){Value = state.ChatTitle ?? string.Empty});
sessionCommand.Parameters.Add(new NpgsqlParameter("@FormUri", DbType.StringFixedLength) {Value = state.FormUri});
sessionCommand.Parameters.Add(new NpgsqlParameter("@QualifiedName", DbType.StringFixedLength){Value = state.QualifiedName });
sessionCommand.ExecuteNonQuery();
}
}
}
using (var connection = new NpgsqlConnection(ConnectionString))
{
connection.Open();
foreach (var state in container.States)
{
SaveSessionsData(state, connection);
}
}
}
/// <inheritdoc/>
/// <summary>
/// Loads form states from the PostgreSQL database.
/// </summary>
/// <returns>A <see cref="StateContainer"/> containing the loaded form states.</returns>
public StateContainer LoadFormStates()
{
var stateContainer = new StateContainer();
using (var connection = new NpgsqlConnection(ConnectionString))
{
connection.Open();
using (var sessionCommand = connection.CreateCommand())
{
sessionCommand.CommandText = selectAllDevicesSessionsSql;
var sessionTable = new DataTable();
using (var dataAdapter = new NpgsqlDataAdapter(sessionCommand))
{
dataAdapter.Fill(sessionTable);
foreach (DataRow row in sessionTable.Rows)
{
var stateEntry = new StateEntry
{
DeviceId = (long)row["deviceId"],
ChatTitle = row["deviceTitle"].ToString(),
FormUri = row["FormUri"].ToString(),
QualifiedName = row["QualifiedName"].ToString()
};
stateContainer.States.Add(stateEntry);
if (stateEntry.DeviceId > 0)
{
stateContainer.ChatIds.Add(stateEntry.DeviceId);
}
else
{
stateContainer.GroupIds.Add(stateEntry.DeviceId);
}
LoadDataTable(connection, row, stateEntry);
}
}
}
}
return stateContainer;
}
/// <summary>
/// Cleans up old session data in the PostgreSQL database.
/// </summary>
private void Cleanup()
{
using (var connection = new NpgsqlConnection(ConnectionString))
{
connection.Open();
using (var clearCommand = connection.CreateCommand())
{
clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions_data";
clearCommand.ExecuteNonQuery();
}
using (var clearCommand = connection.CreateCommand())
{
clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions";
clearCommand.ExecuteNonQuery();
}
}
}
/// <summary>
/// Saves session data to the PostgreSQL database.
/// </summary>
/// <param name="state">The state entry containing session data to be saved.</param>
/// <param name="connection">The NpgsqlConnection used for the database interaction.</param>
private void SaveSessionsData(StateEntry state, NpgsqlConnection connection)
{
foreach (var data in state.Values)
{
using (var dataCommand = connection.CreateCommand())
{
dataCommand.CommandText = insertIntoSessionsDataSql;
dataCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint) { Value = state.DeviceId });
dataCommand.Parameters.Add(new NpgsqlParameter("@key", DbType.StringFixedLength) { Value = data.Key });
var type = data.Value.GetType();
if (type.IsPrimitive || type == typeof(string))
{
dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = data.Value });
}
else
{
var json = System.Text.Json.JsonSerializer.Serialize(data.Value);
dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = json });
}
dataCommand.Parameters.Add(new NpgsqlParameter("@type", DbType.StringFixedLength) { Value = type.AssemblyQualifiedName });
dataCommand.ExecuteNonQuery();
}
}
}
/// <summary>
/// Loads session data from the PostgreSQL database.
/// </summary>
/// <param name="connection">The NpgsqlConnection used for the database interaction.</param>
/// <param name="row">The DataRow representing a session entry in the main sessions table.</param>
/// <param name="stateEntry">The StateEntry object to which session data will be loaded.</param>
private void LoadDataTable(NpgsqlConnection connection, DataRow row, StateEntry stateEntry)
{
using (var sessionCommand = connection.CreateCommand())
{
sessionCommand.CommandText = selectAllDevicesSessionsDataSql;
sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", row["deviceId"]));
var dataCommandTable = new DataTable();
using (var npgSqlDataAdapter = new NpgsqlDataAdapter(sessionCommand))
{
npgSqlDataAdapter.Fill(dataCommandTable);
foreach (DataRow dataRow in dataCommandTable.Rows)
{
var key = dataRow["key"].ToString();
var type = Type.GetType(dataRow["type"].ToString());
var value = System.Text.Json.JsonSerializer.Deserialize(dataRow["value"].ToString(), type);
stateEntry.Values.Add(key, value);
}
}
}
}
}
}
@@ -0,0 +1,27 @@
# TelegramBotBase.Extensions.Serializer.Database.PostgreSQL
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL/)
[![Telegram chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://www.t.me/tgbotbase)
[![License](https://img.shields.io/github/license/MajMcCloud/telegrambotframework.svg?style=flat-square&maxAge=2592000&label=License)](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
[![Package Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL)
## How to use
```csharp
using TelegramBotBase.Extensions.Serializer.Database.PostgreSQL;
var bot = BotBaseBuilder
.Create()
.WithAPIKey(APIKey)
.DefaultMessageLoop()
.WithStartForm<Start>()
.NoProxy()
.OnlyStart()
.UsePostgreSqlDatabase("localhost", "8181", "telegram_bot")
.UseEnglish()
.Build();
bot.Start();
```
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6</TargetFrameworks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl>
<Copyright>MIT</Copyright>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.1</FileVersion>
<Version>1.0.1</Version>
<Description>
A session serializer for PostgreSQL Server.
</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="TelegramBotBase" Version="6.0.0" />
<PackageReference Include="Npgsql" Version="8.0.3" />
</ItemGroup>
</Project>
@@ -0,0 +1,25 @@
-- Enable uuid-ossp extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create table tgb_devices_sessions
CREATE TABLE tgb_devices_sessions (
deviceId bigint NOT NULL,
deviceTitle character varying(512) NOT NULL,
"FormUri" character varying(512) NOT NULL,
"QualifiedName" character varying(512) NOT NULL,
CONSTRAINT PK_tgb_devices_sessions_1 PRIMARY KEY (deviceId)
);
-- Create table tgb_devices_sessions_data
CREATE TABLE tgb_devices_sessions_data (
Id uuid DEFAULT uuid_generate_v4() NOT NULL,
deviceId bigint NOT NULL,
key character varying(512) NOT NULL,
"value" text NOT NULL,
"type" character varying(512) NOT NULL,
CONSTRAINT PK_tgb_devices_session_data PRIMARY KEY (Id)
);
-- Add default constraint for Id column in tgb_devices_sessions_data
ALTER TABLE tgb_devices_sessions_data
ALTER COLUMN Id SET DEFAULT uuid_generate_v4();
@@ -0,0 +1,41 @@
using System;
using System.IO;
using TelegramBotBase.Builder;
using TelegramBotBase.Builder.Interfaces;
namespace TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson
{
public static class BotBaseBuilderExtensions
{
/// <summary>
/// Using the complex version of .Net JSON, which can serialize all objects.
/// Saves in application directory.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static ILanguageSelectionStage UseNewtonsoftJson(this ISessionSerializationStage builder)
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "states.json");
builder.UseNewtonsoftJson(path);
return builder as BotBaseBuilder;
}
/// <summary>
/// Using the complex version of .Net JSON, which can serialize all objects.
/// Saves in application directory.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static ILanguageSelectionStage UseNewtonsoftJson(this ISessionSerializationStage builder, String path)
{
var _stateMachine = new NewtonsoftJsonStateMachine(path);
builder.UseSerialization(_stateMachine);
return builder as BotBaseBuilder;
}
}
}
@@ -0,0 +1,94 @@
using System;
using System.Data;
using System.IO;
using Newtonsoft.Json;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson
{
/// <summary>
/// Is used for all complex data types. Use if other default machines are not working.
/// </summary>
public class NewtonsoftJsonStateMachine : IStateMachine
{
/// <summary>
/// Will initialize the state machine.
/// </summary>
/// <param name="file">Path of the file and name where to save the session details.</param>
/// <param name="fallbackStateForm">
/// Type of Form which will be saved instead of Form which has
/// <seealso cref="Attributes.IgnoreState" /> attribute declared. Needs to be subclass of
/// <seealso cref="Form.FormBase" />.
/// </param>
/// <param name="overwrite">Declares of the file could be overwritten.</param>
public NewtonsoftJsonStateMachine(string file, Type fallbackStateForm = null, bool overwrite = true)
{
FallbackStateForm = fallbackStateForm;
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
FilePath = file ?? throw new ArgumentNullException(nameof(file));
Overwrite = overwrite;
}
public string FilePath { get; set; }
public bool Overwrite { get; set; }
public Type FallbackStateForm { get; }
public StateContainer LoadFormStates()
{
try
{
var content = File.ReadAllText(FilePath);
var sc = JsonConvert.DeserializeObject<StateContainer>(content, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
});
return sc;
}
catch
{
}
return new StateContainer();
}
public void SaveFormStates(SaveStatesEventArgs e)
{
if (File.Exists(FilePath))
{
if (!Overwrite)
{
throw new Exception("File exists already.");
}
File.Delete(FilePath);
}
try
{
var content = JsonConvert.SerializeObject(e.States, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
});
File.WriteAllText(FilePath, content);
}
catch
{
}
}
}
}
@@ -0,0 +1,31 @@
# TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson/)
[![Telegram chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://www.t.me/tgbotbase)
[![License](https://img.shields.io/github/license/MajMcCloud/telegrambotframework.svg?style=flat-square&maxAge=2592000&label=License)](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
[![Package Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson)
### Legacy version to recover with old dependencies using Newtonsoft.Json for session serialization
## How to use
```csharp
using TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson;
var bot = BotBaseBuilder
.Create()
.WithAPIKey(APIKey)
.DefaultMessageLoop()
.WithStartForm<Start>()
.NoProxy()
.OnlyStart()
.UseNewtonsoftJson()
.UseEnglish()
.Build();
bot.Start();
```
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6;net7;net8;net9</TargetFrameworks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl>
<Copyright>MIT</Copyright>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.1</FileVersion>
<Version>1.0.1</Version>
<Description>A session serializer for Newtonsoft Json.
</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="TelegramBotBase" Version="6.0.0" />
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<OutputItemType>Analyzer</OutputItemType>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Telegram.Bot.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Resources;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using TelegramBotBase.SourceGenerators;
namespace TelegramBotBase
{
[Generator(LanguageNames.CSharp)]
public class TelegramDeviceExtensionGenerator : IIncrementalGenerator
{
static XmlDocumentationLoader xml;
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxProvider.CreateSyntaxProvider(
predicate: (c, _) => c is ClassDeclarationSyntax,
transform: (n, _) => (ClassDeclarationSyntax)n.Node)
.Where(a => a is not null);
var compilation = context.CompilationProvider;
context.RegisterSourceOutput(compilation, (spc, source) => Execute(spc, source));
}
private void Execute(SourceProductionContext context, Compilation compilation)
{
//if (!Debugger.IsAttached) Debugger.Launch();
StringBuilder sb = new StringBuilder();
sb.AppendLine();
//Search for reference library
var telegram_package = compilation.References.FirstOrDefault(a => a.Display != null && a.Display.Contains("Telegram.Bot"));
if (telegram_package == null)
return;
//Load only once
if (xml == null)
{
xml = new XmlDocumentationLoader();
xml.ReadEmbeddedXml("Telegram.Bot.xml");
}
var assemblySymbol = compilation.GetAssemblyOrModuleSymbol(telegram_package) as IAssemblySymbol;
if (assemblySymbol == null)
return;
if (assemblySymbol.Name != "Telegram.Bot")
return;
//Get class which includes the existing methods
var apiClass = assemblySymbol.GetTypeByMetadataName("Telegram.Bot.TelegramBotClientExtensions");
if (apiClass == null)
return;
//Get existing list of methods
var methods = apiClass.GetMembers().OfType<IMethodSymbol>().ToList();
foreach (var method in methods)
{
if (!method.Parameters.Any(a => a.Type.Name == "ITelegramBotClient"))
continue;
if (!method.Parameters.Any(a => a.Type.Name == "ChatId"))
continue;
if (method.Name == ".ctor")
continue;
String parameters = "";
String subCallParameters = "";
foreach (var par in method.Parameters)
{
if (par.Name == "botClient")
continue;
if (!string.IsNullOrEmpty(parameters))
{
parameters += ", ";
}
if (!string.IsNullOrEmpty(subCallParameters))
{
subCallParameters += ", ";
}
if (par.Name == "chatId")
{
subCallParameters += $"device.DeviceId";
continue;
}
subCallParameters += $"{par.Name}";
parameters += $"{par.Type.ToDisplayString()} {par.Name}";
if (par.HasExplicitDefaultValue)
{
var defaultValue = par.ExplicitDefaultValue;
// Handle specific default value cases
if (defaultValue == null)
{
if(par.Name == "cancellationToken")
{
parameters += " = default";
}
else
{
parameters += " = null";
}
}
else if (defaultValue is string)
{
parameters += $" = \"{defaultValue}\""; // Add quotes around string default values
}
else if (defaultValue is bool)
{
parameters += $" = {defaultValue.ToString().ToLower()}"; // Use lower case for booleans (true/false)
}
else if (defaultValue is char)
{
parameters += $" = '{defaultValue}'"; // Use single quotes for char
}
else
{
parameters += $" = {defaultValue}"; // General case for other types (numbers, enums, etc.)
}
}
}
var returnStatement = "";
if (method.ReturnType is INamedTypeSymbol namedType && namedType.IsGenericType && namedType.ConstructedFrom.Name == "Task" && namedType.ContainingNamespace.ToDisplayString() == "System.Threading.Tasks")
{
returnStatement = "return await";
}
else if (method.ReturnType.Name == "Task" && method.ReturnType.ContainingNamespace.ToDisplayString() == "System.Threading.Tasks")
{
returnStatement = "await";
}
else if (method.ReturnsVoid)
{
returnStatement = "";
}
else
{
returnStatement = "return ";
}
String tmp = GenerateMethod(method, parameters, subCallParameters, returnStatement);
sb.Append(tmp);
}
//The generated source
var sourceCode = $$"""
using System;
using System.Threading.Tasks;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
using Telegram.Bot;
using Telegram.Bot.Extensions;
using Telegram.Bot.Requests;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.InlineQueryResults;
using Telegram.Bot.Types.Payments;
using Telegram.Bot.Types.ReplyMarkups;
using File = Telegram.Bot.Types.File;
#nullable enable
namespace TelegramBotBase;
public static class DeviceExtensions
{
{{sb.ToString()}}
}
""";
//Cleanup
sourceCode = sourceCode.Replace("System.Threading.Tasks.", "");
context.AddSource("DeviceExtensions.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
}
/// <summary>
/// Test
/// </summary>
/// <param name="method"></param>
/// <param name="parameters"></param>
/// <param name="subCallParameters"></param>
/// <param name="returnStatement"></param>
/// <returns></returns>
private String GenerateMethod(IMethodSymbol? method, string parameters, string subCallParameters, string returnStatement)
{
//Adding xml comments from embedded xml file (Workaround)
String xml_comments = xml?.GetDocumentationLinesForSymbol(method);
StringBuilder sb = new StringBuilder();
sb.AppendLine(xml_comments);
sb.AppendLine($" public static async {method.ReturnType.ToDisplayString()} {method.Name}(this IDeviceSession device, {parameters})");
sb.AppendLine($" {{");
sb.AppendLine($" {returnStatement} device.Client.TelegramClient.{method.Name}({subCallParameters});");
sb.AppendLine($" }}");
sb.AppendLine();
sb.AppendLine();
return sb.ToString();
}
}
}
@@ -0,0 +1,89 @@
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;
namespace TelegramBotBase.SourceGenerators
{
public class XmlDocumentationLoader
{
XDocument xDocument;
public string GetDocumentationLinesForSymbol(ISymbol symbol)
{
var docElement = xDocument?.Descendants("member")
.FirstOrDefault(e => e.Attribute("name")?.Value == GetDocumentationCommentId(symbol));
StringBuilder sb = new StringBuilder();
XNode first = docElement.FirstNode;
do
{
sb.AppendLine(first.ToString());
first = first.NextNode;
}
while (first.NextNode != null);
var lines = sb.ToString().Split('\n');
sb = new StringBuilder();
foreach (var line in lines)
{
if (line == "")
continue;
sb.AppendLine($" /// {line.Trim()}");
}
return sb.ToString().Trim();
}
private string GetDocumentationCommentId(ISymbol symbol)
{
// Returns the documentation comment ID for a symbol
return symbol.GetDocumentationCommentId();
}
public XDocument ReadEmbeddedXml(string resourceName)
{
// Get the assembly where the resource is embedded
Assembly assembly = Assembly.GetExecutingAssembly();
// Construct the full resource name
string fullResourceName = $"{assembly.GetName().Name}.Resources.{resourceName}";
var names = assembly.GetManifestResourceNames();
if (!names.Contains(fullResourceName))
return null;
// Open a stream to the embedded resource
using (Stream stream = assembly.GetManifestResourceStream(fullResourceName))
{
if (stream == null)
{
//throw new FileNotFoundException("Resource not found", fullResourceName);
return null;
}
xDocument = XDocument.Load(stream);
// Load the stream into an XDocument
return xDocument;
}
}
}
}
+1
View File
@@ -32,6 +32,7 @@ internal class Program
}) })
.NoSerialization() .NoSerialization()
.UseEnglish() .UseEnglish()
.UseThreadPool()
.Build(); .Build();
@@ -8,7 +8,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\TelegramBotBase.Extensions.Images\TelegramBotBase.Extensions.Images.csproj" /> <PackageReference Include="TelegramBotBase.Extensions.Images" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" /> <ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup> </ItemGroup>
@@ -11,7 +11,7 @@ namespace TelegramBotBase.Example.Tests.Controls;
public class LabelForm : AutoCleanForm public class LabelForm : AutoCleanForm
{ {
TelegramBotBase.Controls.Inline.LabelControl _label; TelegramBotBase.Controls.Inline.Label _label;
ButtonGrid _buttonGrid; ButtonGrid _buttonGrid;
@@ -27,7 +27,7 @@ public class LabelForm : AutoCleanForm
private Task LabelForm_Init(object sender, InitEventArgs e) private Task LabelForm_Init(object sender, InitEventArgs e)
{ {
//The "label" control //The "label" control
_label = new LabelControl("My test label"); _label = new Label("My test label");
AddControl(_label); AddControl(_label);
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Text.Json;
using TelegramBotBase.Controls.Hybrid; using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.DataSources; using TelegramBotBase.DataSources;
using TelegramBotBase.Form; using TelegramBotBase.Form;
@@ -36,7 +36,7 @@ public class CustomDataSource : ButtonFormDataSource
{ {
try try
{ {
var list = JsonConvert.DeserializeObject<List<string>>(File.ReadAllText("countries.json")); var list = JsonSerializer.Deserialize<List<string>>(File.ReadAllText("countries.json"));
Countries = list; Countries = list;
@@ -56,7 +56,7 @@ public class CustomDataSource : ButtonFormDataSource
Countries = countries; Countries = countries;
var tmp = JsonConvert.SerializeObject(countries); var tmp = JsonSerializer.Serialize(countries);
File.WriteAllText(AppContext.BaseDirectory + "countries.json", tmp); File.WriteAllText(AppContext.BaseDirectory + "countries.json", tmp);
} }
+3 -3
View File
@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Args; namespace TelegramBotBase.Args;
@@ -15,7 +15,7 @@ public class BotCommandEventArgs : EventArgs
} }
public BotCommandEventArgs(string command, List<string> parameters, Message message, long deviceId, public BotCommandEventArgs(string command, List<string> parameters, Message message, long deviceId,
DeviceSession device) IDeviceSession device)
{ {
Command = command; Command = command;
Parameters = parameters; Parameters = parameters;
@@ -30,7 +30,7 @@ public class BotCommandEventArgs : EventArgs
public long DeviceId { get; set; } public long DeviceId { get; set; }
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
public bool Handled { get; set; } = false; public bool Handled { get; set; } = false;
@@ -1,11 +1,11 @@
using System; using System;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Base; namespace TelegramBotBase.Base;
public class MessageIncomeEventArgs : EventArgs public class MessageIncomeEventArgs : EventArgs
{ {
public MessageIncomeEventArgs(long deviceId, DeviceSession device, MessageResult message) public MessageIncomeEventArgs(long deviceId, IDeviceSession device, MessageResult message)
{ {
DeviceId = deviceId; DeviceId = deviceId;
Device = device; Device = device;
@@ -14,7 +14,7 @@ public class MessageIncomeEventArgs : EventArgs
public long DeviceId { get; set; } public long DeviceId { get; set; }
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
public MessageResult Message { get; set; } public MessageResult Message { get; set; }
} }
@@ -1,11 +1,11 @@
using System; using System;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Base; namespace TelegramBotBase.Base;
public class SessionBeginEventArgs : EventArgs public class SessionBeginEventArgs : EventArgs
{ {
public SessionBeginEventArgs(long deviceId, DeviceSession device) public SessionBeginEventArgs(long deviceId, IDeviceSession device)
{ {
DeviceId = deviceId; DeviceId = deviceId;
Device = device; Device = device;
@@ -13,5 +13,5 @@ public class SessionBeginEventArgs : EventArgs
public long DeviceId { get; set; } public long DeviceId { get; set; }
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
} }
@@ -1,5 +1,5 @@
using System; using System;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Args; namespace TelegramBotBase.Args;
@@ -9,7 +9,7 @@ public class SystemExceptionEventArgs : EventArgs
{ {
} }
public SystemExceptionEventArgs(string command, long deviceId, DeviceSession device, Exception error) public SystemExceptionEventArgs(string command, long deviceId, IDeviceSession device, Exception error)
{ {
Command = command; Command = command;
DeviceId = deviceId; DeviceId = deviceId;
@@ -21,7 +21,7 @@ public class SystemExceptionEventArgs : EventArgs
public long DeviceId { get; set; } public long DeviceId { get; set; }
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
public Exception Error { get; set; } public Exception Error { get; set; }
} }
@@ -1,6 +1,6 @@
using System; using System;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Args; namespace TelegramBotBase.Args;
@@ -12,7 +12,7 @@ public class UnhandledCallEventArgs : EventArgs
} }
public UnhandledCallEventArgs(string command, string rawData, long deviceId, int messageId, Message message, public UnhandledCallEventArgs(string command, string rawData, long deviceId, int messageId, Message message,
DeviceSession device) : this() IDeviceSession device) : this()
{ {
Command = command; Command = command;
RawData = rawData; RawData = rawData;
@@ -26,7 +26,7 @@ public class UnhandledCallEventArgs : EventArgs
public long DeviceId { get; set; } public long DeviceId { get; set; }
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
public string RawData { get; set; } public string RawData { get; set; }
+2 -2
View File
@@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Base; namespace TelegramBotBase.Base;
@@ -8,7 +8,7 @@ namespace TelegramBotBase.Base;
/// </summary> /// </summary>
public class ControlBase public class ControlBase
{ {
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
public int Id { get; set; } public int Id { get; set; }
+14
View File
@@ -0,0 +1,14 @@
using System;
namespace TelegramBotBase.Base
{
public class ErrorResult : EventArgs
{
public ErrorResult(Exception exception)
{
Exception = exception;
}
public Exception Exception { get; }
}
}
+4 -2
View File
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Form.Navigation; using TelegramBotBase.Form.Navigation;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
using static TelegramBotBase.Base.Async; using static TelegramBotBase.Base.Async;
namespace TelegramBotBase.Form; namespace TelegramBotBase.Form;
@@ -28,10 +28,12 @@ public class FormBase : IDisposable
public NavigationController NavigationController { get; set; } public NavigationController NavigationController { get; set; }
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
public MessageClient Client { get; set; } public MessageClient Client { get; set; }
IServiceProvider _serviceProvider = null;
/// <summary> /// <summary>
/// has this formular already been disposed ? /// has this formular already been disposed ?
/// </summary> /// </summary>
+69 -45
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot; using Telegram.Bot;
@@ -10,6 +11,7 @@ using Telegram.Bot.Exceptions;
using Telegram.Bot.Polling; using Telegram.Bot.Polling;
using Telegram.Bot.Types; using Telegram.Bot.Types;
namespace TelegramBotBase.Base; namespace TelegramBotBase.Base;
/// <summary> /// <summary>
@@ -17,31 +19,38 @@ namespace TelegramBotBase.Base;
/// </summary> /// </summary>
public class MessageClient public class MessageClient
{ {
private EventHandlerList Events { get; } = new();
private static readonly object EvOnMessageLoop = new(); private static readonly object EvOnMessageLoop = new();
private static readonly object EvOnReceiveError = new();
private static object __evOnMessage = new(); protected CancellationTokenSource _cancellationTokenSource;
private static object __evOnMessageEdit = new(); public string ApiKey { get; }
private static object __evCallbackQuery = new(); public ITelegramBotClient TelegramClient { get; set; }
private CancellationTokenSource _cancellationTokenSource;
/// <summary>
/// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before
// start polling. If set to true Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates
// should be set to not null, otherwise Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates
// will effectively be set to receive all Telegram.Bot.Types.Updates.
/// </summary>
public bool ThrowPendingUpdates { get; set; }
public MessageClient(string apiKey) public MessageClient(string apiKey)
{ {
ApiKey = apiKey; ApiKey = apiKey;
TelegramClient = new TelegramBotClient(apiKey); TelegramClient = new TelegramBotClient(apiKey);
Prepare();
} }
public MessageClient(string apiKey, HttpClient proxy) public MessageClient(string apiKey, HttpClient proxy)
{ {
ApiKey = apiKey; ApiKey = apiKey;
TelegramClient = new TelegramBotClient(apiKey, proxy); TelegramClient = new TelegramBotClient(apiKey, proxy);
Prepare();
} }
@@ -59,8 +68,6 @@ public class MessageClient
); );
TelegramClient = new TelegramBotClient(apiKey, httpClient); TelegramClient = new TelegramBotClient(apiKey, httpClient);
Prepare();
} }
/// <summary> /// <summary>
@@ -80,8 +87,6 @@ public class MessageClient
); );
TelegramClient = new TelegramBotClient(apiKey, httpClient); TelegramClient = new TelegramBotClient(apiKey, httpClient);
Prepare();
} }
@@ -89,61 +94,41 @@ public class MessageClient
{ {
ApiKey = apiKey; ApiKey = apiKey;
TelegramClient = client; TelegramClient = client;
Prepare();
} }
public string ApiKey { get; } public virtual void StartReceiving()
public ITelegramBotClient TelegramClient { get; set; }
private EventHandlerList Events { get; } = new();
public void Prepare()
{
TelegramClient.Timeout = new TimeSpan(0, 0, 30);
}
public void StartReceiving()
{ {
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
var receiverOptions = new ReceiverOptions(); var receiverOptions = new ReceiverOptions();
TelegramClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions, receiverOptions.ThrowPendingUpdates = ThrowPendingUpdates;
_cancellationTokenSource.Token);
TelegramClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions, _cancellationTokenSource.Token);
} }
public void StopReceiving()
public virtual void StopReceiving()
{ {
_cancellationTokenSource.Cancel(); _cancellationTokenSource?.Cancel();
} }
public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) private async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{ {
await OnMessageLoop(new UpdateResult(update, null)); await OnMessageLoop(new UpdateResult(update, null));
} }
public Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception,
private async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (exception is ApiRequestException exApi) await OnReceiveError(new ErrorResult(exception));
{
Console.WriteLine($"Telegram API Error:\n[{exApi.ErrorCode}]\n{exApi.Message}");
}
else
{
Console.WriteLine(exception.ToString());
}
return Task.CompletedTask;
} }
#region "BotCommands"
/// <summary> /// <summary>
/// This will return the current list of bot commands. /// This will return the current list of bot commands.
/// </summary> /// </summary>
@@ -175,6 +160,8 @@ public class MessageClient
await TelegramClient.DeleteMyCommandsAsync(scope, languageCode); await TelegramClient.DeleteMyCommandsAsync(scope, languageCode);
} }
#endregion
#region "Events" #region "Events"
@@ -184,10 +171,47 @@ public class MessageClient
remove => Events.RemoveHandler(EvOnMessageLoop, value); remove => Events.RemoveHandler(EvOnMessageLoop, value);
} }
public async Task OnMessageLoop(UpdateResult update) public async Task OnMessageLoop(UpdateResult update)
{ {
await (Events[EvOnMessageLoop] as Async.AsyncEventHandler<UpdateResult>)?.Invoke(this, update); var eventHandlers = (Events[EvOnMessageLoop] as Async.AsyncEventHandler<UpdateResult>)?.Invoke(this, update);
if (eventHandlers != null)
{
await eventHandlers;
}
}
public event Async.AsyncEventHandler<ErrorResult> ReceiveError
{
add => Events.AddHandler(EvOnReceiveError, value);
remove => Events.RemoveHandler(EvOnReceiveError, value);
}
public async Task OnReceiveError(ErrorResult update)
{
var eventHandlers = (Events[EvOnReceiveError] as Async.AsyncEventHandler<ErrorResult>)?.Invoke(this, update);
if (eventHandlers != null)
{
await eventHandlers;
return;
}
//Fallback when no event handler is used.
if (update.Exception is ApiRequestException exApi)
{
Console.WriteLine($"Telegram API Error:\n[{exApi.ErrorCode}]\n{exApi.Message}");
}
else
{
Console.WriteLine(update.Exception.ToString());
}
} }
#endregion #endregion
} }
+37 -22
View File
@@ -1,7 +1,8 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
@@ -9,16 +10,36 @@ namespace TelegramBotBase.Base;
public class MessageResult : ResultBase public class MessageResult : ResultBase
{ {
internal MessageResult()
{
}
public MessageResult(Update update) public MessageResult(Update update)
{ {
UpdateData = update; UpdateData = update;
init();
} }
public Update UpdateData { get; set; } void init()
{
IsAction = UpdateData.CallbackQuery != null;
if (Message == null)
return;
IsBotCommand = Message.Entities?.Any(a => a.Type == MessageEntityType.BotCommand) ?? false;
if (!IsBotCommand)
return;
BotCommand = MessageText.Split(' ')[0];
IsBotGroupCommand = BotCommand.Contains("@");
if (IsBotGroupCommand)
{
BotCommand = BotCommand.Substring(0, BotCommand.LastIndexOf('@'));
}
}
public Update UpdateData { get; private set; }
/// <summary> /// <summary>
/// Returns the Device/ChatId /// Returns the Device/ChatId
@@ -55,12 +76,17 @@ public class MessageResult : ResultBase
/// <summary> /// <summary>
/// Is this an action ? (i.e. button click) /// Is this an action ? (i.e. button click)
/// </summary> /// </summary>
public bool IsAction => UpdateData.CallbackQuery != null; public bool IsAction { get; private set; }
/// <summary> /// <summary>
/// Is this a command ? Starts with a slash '/' and a command /// Is this a command ? Starts with a slash '/' and a command
/// </summary> /// </summary>
public bool IsBotCommand => MessageText.StartsWith("/"); public bool IsBotCommand { get; private set; }
/// <summary>
/// Is this a bot command sent from a group via @BotId ?
/// </summary>
public bool IsBotGroupCommand { get; private set; }
/// <summary> /// <summary>
/// Returns a List of all parameters which has been sent with the command itself (i.e. /start 123 456 789 => /// Returns a List of all parameters which has been sent with the command itself (i.e. /start 123 456 789 =>
@@ -83,18 +109,7 @@ public class MessageResult : ResultBase
/// <summary> /// <summary>
/// Returns just the command (i.e. /start 1 2 3 => /start) /// Returns just the command (i.e. /start 1 2 3 => /start)
/// </summary> /// </summary>
public string BotCommand public string BotCommand { get; private set; }
{
get
{
if (!IsBotCommand)
{
return null;
}
return MessageText.Split(' ')[0];
}
}
/// <summary> /// <summary>
/// Returns if this message will be used on the first form or not. /// Returns if this message will be used on the first form or not.
@@ -111,7 +126,7 @@ public class MessageResult : ResultBase
T cd = null; T cd = null;
try try
{ {
cd = JsonConvert.DeserializeObject<T>(RawData); cd = JsonSerializer.Deserialize<T>(RawData);
return cd; return cd;
} }
+2 -2
View File
@@ -2,13 +2,13 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Base; namespace TelegramBotBase.Base;
public class ResultBase : EventArgs public class ResultBase : EventArgs
{ {
public DeviceSession Device { get; set; } public IDeviceSession Device { get; set; }
public virtual long DeviceId { get; set; } public virtual long DeviceId { get; set; }
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Exceptions;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Base;
/// <summary>
/// Base class for message handling
/// </summary>
public class ThreadPoolMessageClient : MessageClient
{
/// <summary>
/// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before
// start polling. If set to true Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates
// should be set to not null, otherwise Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates
// will effectively be set to receive all Telegram.Bot.Types.Updates.
/// </summary>
public int ThreadPool_WorkerThreads { get; set; } = 1;
public int ThreadPool_IOThreads { get; set; } = 1;
public ThreadPoolMessageClient(string apiKey) : base(apiKey)
{
}
public ThreadPoolMessageClient(string apiKey, HttpClient proxy) : base(apiKey, proxy)
{
}
public ThreadPoolMessageClient(string apiKey, Uri proxyUrl, NetworkCredential credential = null) : base(apiKey, proxyUrl, credential)
{
}
/// <summary>
/// Initializes the client with a proxy
/// </summary>
/// <param name="apiKey"></param>
/// <param name="proxyHost">i.e. 127.0.0.1</param>
/// <param name="proxyPort">i.e. 10000</param>
public ThreadPoolMessageClient(string apiKey, string proxyHost, int proxyPort) : base(apiKey, proxyHost, proxyPort)
{
}
public ThreadPoolMessageClient(string apiKey, TelegramBotClient client) : base(apiKey, client)
{
}
public override void StartReceiving()
{
_cancellationTokenSource = new CancellationTokenSource();
var receiverOptions = new ReceiverOptions();
receiverOptions.ThrowPendingUpdates = ThrowPendingUpdates;
ThreadPool.SetMaxThreads(ThreadPool_WorkerThreads, ThreadPool_IOThreads);
TelegramClient.StartReceiving(HandleUpdateAsyncThreadPool, HandleErrorAsyncThreadPool, receiverOptions, _cancellationTokenSource.Token);
}
public override void StopReceiving()
{
_cancellationTokenSource?.Cancel();
}
public Task HandleUpdateAsyncThreadPool(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
ThreadPool.QueueUserWorkItem(async a =>
{
await OnMessageLoop(new UpdateResult(update, null));
});
return Task.CompletedTask;
}
public Task HandleErrorAsyncThreadPool(ITelegramBotClient botClient, Exception exception,
CancellationToken cancellationToken)
{
ThreadPool.QueueUserWorkItem(async a =>
{
await OnReceiveError(new ErrorResult(exception));
});
return Task.CompletedTask;
}
}
+3 -2
View File
@@ -1,11 +1,11 @@
using Telegram.Bot.Types; using Telegram.Bot.Types;
using TelegramBotBase.Sessions; using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Base; namespace TelegramBotBase.Base;
public class UpdateResult : ResultBase public class UpdateResult : ResultBase
{ {
public UpdateResult(Update rawData, DeviceSession device) public UpdateResult(Update rawData, IDeviceSession device)
{ {
RawData = rawData; RawData = rawData;
Device = device; Device = device;
@@ -16,6 +16,7 @@ public class UpdateResult : ResultBase
/// </summary> /// </summary>
public override long DeviceId => public override long DeviceId =>
RawData?.Message?.Chat?.Id RawData?.Message?.Chat?.Id
?? RawData?.EditedMessage?.Chat?.Id
?? RawData?.CallbackQuery?.Message?.Chat?.Id ?? RawData?.CallbackQuery?.Message?.Chat?.Id
?? Device?.DeviceId ?? Device?.DeviceId
?? 0; ?? 0;
+15 -9
View File
@@ -8,6 +8,7 @@ using Telegram.Bot.Types;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Enums; using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions;
using TelegramBotBase.Interfaces; using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions; using TelegramBotBase.Sessions;
using Console = TelegramBotBase.Tools.Console; using Console = TelegramBotBase.Tools.Console;
@@ -115,12 +116,13 @@ public sealed class BotBase
if (ds == null) if (ds == null)
{ {
ds = await Sessions.StartSession(e.DeviceId); ds = await Sessions.StartSession(e.DeviceId);
e.Device = ds; ds.LastMessage = e.Message;
ds.LastMessage = e.RawData.Message;
OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds)); OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds));
} }
e.Device = ds;
var mr = new MessageResult(e.RawData); var mr = new MessageResult(e.RawData);
var i = 0; var i = 0;
@@ -138,6 +140,13 @@ public sealed class BotBase
mr.IsFirstHandler = false; mr.IsFirstHandler = false;
} while (ds.FormSwitched && i < GetSetting(ESettings.NavigationMaximum, 10)); } while (ds.FormSwitched && i < GetSetting(ESettings.NavigationMaximum, 10));
} }
catch (InvalidServiceProviderConfiguration ex)
{
var ds = Sessions.GetSession(e.DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, e.DeviceId, ds, ex));
throw;
}
catch (Exception ex) catch (Exception ex)
{ {
var ds = Sessions.GetSession(e.DeviceId); var ds = Sessions.GetSession(e.DeviceId);
@@ -188,13 +197,10 @@ public sealed class BotBase
/// <param name="deviceId">Contains the device/chat id of the device to update.</param> /// <param name="deviceId">Contains the device/chat id of the device to update.</param>
public async Task InvokeMessageLoop(long deviceId) public async Task InvokeMessageLoop(long deviceId)
{ {
var mr = new MessageResult var mr = new MessageResult(new Update
{ {
UpdateData = new Update Message = new Message()
{ });
Message = new Message()
}
};
await InvokeMessageLoop(deviceId, mr); await InvokeMessageLoop(deviceId, mr);
} }
@@ -259,7 +265,7 @@ public sealed class BotBase
{ {
foreach (var scope in BotCommandScopes) foreach (var scope in BotCommandScopes)
{ {
if (scope.Value.Any(a => "/" + a.Command == command)) if (scope.Value.Any(a => Constants.Telegram.BotCommandIndicator + a.Command == command))
{ {
return true; return true;
} }
+80 -22
View File
@@ -18,7 +18,7 @@ namespace TelegramBotBase.Builder;
public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, IStartFormSelectionStage, public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, IStartFormSelectionStage,
IBuildingStage, INetworkingSelectionStage, IBotCommandsStage, ISessionSerializationStage, IBuildingStage, INetworkingSelectionStage, IBotCommandsStage, ISessionSerializationStage,
ILanguageSelectionStage ILanguageSelectionStage, IThreadingStage
{ {
private string _apiKey; private string _apiKey;
@@ -32,6 +32,7 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
private BotBaseBuilder() private BotBaseBuilder()
{ {
} }
/// <summary> /// <summary>
@@ -39,7 +40,10 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
/// </summary> /// </summary>
private Dictionary<BotCommandScope, List<BotCommand>> BotCommandScopes { get; } = new(); private Dictionary<BotCommandScope, List<BotCommand>> BotCommandScopes { get; } = new();
/// <summary>
/// Creates a full BotBase instance with all parameters previously set.
/// </summary>
/// <returns></returns>
public BotBase Build() public BotBase Build()
{ {
var bot = new BotBase(_apiKey, _client) var bot = new BotBase(_apiKey, _client)
@@ -69,14 +73,14 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
} }
public IBuildingStage QuickStart(string apiKey, Type startForm) public IBuildingStage QuickStart(string apiKey, Type startForm, bool throwPendingUpdates = false)
{ {
_apiKey = apiKey; _apiKey = apiKey;
_factory = new DefaultStartFormFactory(startForm); _factory = new DefaultStartFormFactory(startForm);
DefaultMessageLoop(); DefaultMessageLoop();
NoProxy(); NoProxy(throwPendingUpdates);
OnlyStart(); OnlyStart();
@@ -84,11 +88,13 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
DefaultLanguage(); DefaultLanguage();
UseSingleThread();
return this; return this;
} }
public IBuildingStage QuickStart<T>(string apiKey) public IBuildingStage QuickStart<T>(string apiKey, bool throwPendingUpdates = false)
where T : FormBase where T : FormBase
{ {
_apiKey = apiKey; _apiKey = apiKey;
@@ -96,7 +102,7 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
DefaultMessageLoop(); DefaultMessageLoop();
NoProxy(); NoProxy(throwPendingUpdates);
OnlyStart(); OnlyStart();
@@ -104,17 +110,19 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
DefaultLanguage(); DefaultLanguage();
UseSingleThread();
return this; return this;
} }
public IBuildingStage QuickStart(string apiKey, IStartFormFactory startFormFactory) public IBuildingStage QuickStart(string apiKey, IStartFormFactory startFormFactory, bool throwPendingUpdates = false)
{ {
_apiKey = apiKey; _apiKey = apiKey;
_factory = startFormFactory; _factory = startFormFactory;
DefaultMessageLoop(); DefaultMessageLoop();
NoProxy(); NoProxy(throwPendingUpdates);
OnlyStart(); OnlyStart();
@@ -122,6 +130,8 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
DefaultLanguage(); DefaultLanguage();
UseSingleThread();
return this; return this;
} }
@@ -204,67 +214,72 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
#region "Step 4 (Network Settings)" #region "Step 4 (Network Settings)"
public IBotCommandsStage WithProxy(string proxyAddress) public IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false, int timeoutInSeconds = 60)
{ {
var url = new Uri(proxyAddress); var url = new Uri(proxyAddress);
_client = new MessageClient(_apiKey, url) _client = new MessageClient(_apiKey, url)
{ {
TelegramClient = TelegramClient =
{ {
Timeout = new TimeSpan(0, 1, 0) Timeout = TimeSpan.FromSeconds(timeoutInSeconds)
}, },
}; };
_client.ThrowPendingUpdates = throwPendingUpdates;
return this; return this;
} }
public IBotCommandsStage NoProxy() public IBotCommandsStage NoProxy(bool throwPendingUpdates = false, int timeoutInSeconds = 60)
{ {
_client = new MessageClient(_apiKey) _client = new MessageClient(_apiKey)
{ {
TelegramClient = TelegramClient =
{ {
Timeout = new TimeSpan(0, 1, 0) Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0)
} }
}; };
_client.ThrowPendingUpdates = throwPendingUpdates;
return this; return this;
} }
public IBotCommandsStage WithBotClient(TelegramBotClient tgclient) public IBotCommandsStage WithBotClient(TelegramBotClient tgclient, bool throwPendingUpdates = false, int timeoutInSeconds = 60)
{ {
_client = new MessageClient(_apiKey, tgclient) _client = new MessageClient(_apiKey, tgclient)
{ {
TelegramClient = TelegramClient =
{ {
Timeout = new TimeSpan(0, 1, 0) Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0)
} }
}; };
_client.ThrowPendingUpdates = throwPendingUpdates;
return this; return this;
} }
public IBotCommandsStage WithHostAndPort(string proxyHost, int proxyPort) public IBotCommandsStage WithHostAndPort(string proxyHost, int proxyPort, bool throwPendingUpdates = false, int timeoutInSeconds = 60)
{ {
_client = new MessageClient(_apiKey, proxyHost, proxyPort) _client = new MessageClient(_apiKey, proxyHost, proxyPort)
{ {
TelegramClient = TelegramClient =
{ {
Timeout = new TimeSpan(0, 1, 0) Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0)
} }
}; };
_client.ThrowPendingUpdates = throwPendingUpdates;
return this; return this;
} }
public IBotCommandsStage WithHttpClient(HttpClient tgclient) public IBotCommandsStage WithHttpClient(HttpClient tgclient, bool throwPendingUpdates = false, int timeoutInSeconds = 60)
{ {
_client = new MessageClient(_apiKey, tgclient) _client = new MessageClient(_apiKey, tgclient)
{ {
TelegramClient = TelegramClient =
{ {
Timeout = new TimeSpan(0, 1, 0) Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0)
} }
}; };
_client.ThrowPendingUpdates = throwPendingUpdates;
return this; return this;
} }
@@ -383,28 +398,71 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
#region "Step 7 (Language)" #region "Step 7 (Language)"
public IBuildingStage DefaultLanguage() /// <inheritdoc cref="ILanguageSelectionStage.DefaultLanguage"/>
public IThreadingStage DefaultLanguage()
{ {
return this; return this;
} }
public IBuildingStage UseEnglish() /// <inheritdoc cref="ILanguageSelectionStage.UseEnglish"/>
public IThreadingStage UseEnglish()
{ {
Default.Language = new English(); Default.Language = new English();
return this; return this;
} }
public IBuildingStage UseGerman() /// <inheritdoc cref="ILanguageSelectionStage.UseGerman"/>
public IThreadingStage UseGerman()
{ {
Default.Language = new German(); Default.Language = new German();
return this; return this;
} }
public IBuildingStage Custom(Localization language) /// <inheritdoc cref="ILanguageSelectionStage.UsePersian"/>
public IThreadingStage UsePersian()
{
Default.Language = new Persian();
return this;
}
/// <inheritdoc cref="ILanguageSelectionStage.UseRussian"/>
public IThreadingStage UseRussian()
{
Default.Language = new Russian();
return this;
}
/// <inheritdoc cref="ILanguageSelectionStage.Custom"/>
public IThreadingStage Custom(Localization language)
{ {
Default.Language = language; Default.Language = language;
return this; return this;
} }
#endregion #endregion
#region "Step 8 (Threading)"
public IBuildingStage UseSingleThread()
{
return this;
}
public IBuildingStage UseThreadPool(int workerThreads = 2, int ioThreads = 1)
{
var c = new ThreadPoolMessageClient(_apiKey, (TelegramBotClient)_client.TelegramClient);
c.ThreadPool_WorkerThreads = workerThreads;
c.ThreadPool_IOThreads = ioThreads;
c.ThrowPendingUpdates = _client.ThrowPendingUpdates;
_client = c;
return this;
}
#endregion
} }
@@ -20,16 +20,18 @@ public interface IAPIKeySelectionStage
/// </summary> /// </summary>
/// <param name="apiKey"></param> /// <param name="apiKey"></param>
/// <param name="StartForm"></param> /// <param name="StartForm"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBuildingStage QuickStart(string apiKey, Type StartForm); IBuildingStage QuickStart(string apiKey, Type StartForm, bool throwPendingUpdates = false);
/// <summary> /// <summary>
/// Quick and easy way to create a BotBase instance. /// Quick and easy way to create a BotBase instance.
/// Uses: DefaultMessageLoop, NoProxy, OnlyStart, NoSerialization, DefaultLanguage /// Uses: DefaultMessageLoop, NoProxy, OnlyStart, NoSerialization, DefaultLanguage
/// </summary> /// </summary>
/// <param name="apiKey"></param> /// <param name="apiKey"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBuildingStage QuickStart<T>(string apiKey) where T : FormBase; IBuildingStage QuickStart<T>(string apiKey , bool throwPendingUpdates = false) where T : FormBase;
/// <summary> /// <summary>
/// Quick and easy way to create a BotBase instance. /// Quick and easy way to create a BotBase instance.
@@ -37,6 +39,7 @@ public interface IAPIKeySelectionStage
/// </summary> /// </summary>
/// <param name="apiKey"></param> /// <param name="apiKey"></param>
/// <param name="StartFormFactory"></param> /// <param name="StartFormFactory"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBuildingStage QuickStart(string apiKey, IStartFormFactory StartFormFactory); IBuildingStage QuickStart(string apiKey, IStartFormFactory StartFormFactory, bool throwPendingUpdates = false);
} }
@@ -2,5 +2,9 @@
public interface IBuildingStage public interface IBuildingStage
{ {
/// <summary>
/// Creates a full BotBase instance with all parameters previously set.
/// </summary>
/// <returns></returns>
BotBase Build(); BotBase Build();
} }
@@ -2,29 +2,44 @@
namespace TelegramBotBase.Builder.Interfaces; namespace TelegramBotBase.Builder.Interfaces;
/// <summary>
/// Represents the language selection stage in the localization process.
/// </summary>
public interface ILanguageSelectionStage public interface ILanguageSelectionStage
{ {
/// <summary> /// <summary>
/// Selects the default language for control usage. (English) /// Selects the default language for control usage. (English)
/// </summary> /// </summary>
/// <returns></returns> /// <returns>The next stage in the building process.</returns>
IBuildingStage DefaultLanguage(); IThreadingStage DefaultLanguage();
/// <summary> /// <summary>
/// Selects english as the default language for control labels. /// Selects english as the default language for control labels.
/// </summary> /// </summary>
/// <returns></returns> /// <returns>The next stage in the building process.</returns>
IBuildingStage UseEnglish(); IThreadingStage UseEnglish();
/// <summary> /// <summary>
/// Selects german as the default language for control labels. /// Selects german as the default language for control labels.
/// </summary> /// </summary>
/// <returns></returns> /// <returns>The next stage in the building process.</returns>
IBuildingStage UseGerman(); IThreadingStage UseGerman();
/// <summary>
/// Selects persian as the default language for control labels.
/// </summary>
/// <returns>The next stage in the building process.</returns>
IThreadingStage UsePersian();
/// <summary>
/// Selects russian as the default language for control labels.
/// </summary>
/// <returns>The next stage in the building process.</returns>
IThreadingStage UseRussian();
/// <summary> /// <summary>
/// Selects a custom language as the default language for control labels. /// Selects a custom language as the default language for control labels.
/// </summary> /// </summary>
/// <returns></returns> /// <returns>The next stage in the building process.</returns>
IBuildingStage Custom(Localization language); IThreadingStage Custom(Localization language);
} }
@@ -9,22 +9,25 @@ public interface INetworkingSelectionStage
/// Chooses a proxy as network configuration. /// Chooses a proxy as network configuration.
/// </summary> /// </summary>
/// <param name="proxyAddress"></param> /// <param name="proxyAddress"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBotCommandsStage WithProxy(string proxyAddress); IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false, int timeoutInSeconds = 60);
/// <summary> /// <summary>
/// Do not choose a proxy as network configuration. /// Do not choose a proxy as network configuration.
/// </summary> /// </summary>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBotCommandsStage NoProxy(); IBotCommandsStage NoProxy(bool throwPendingUpdates = false, int timeoutInSeconds = 60);
/// <summary> /// <summary>
/// Chooses a custom instance of TelegramBotClient. /// Chooses a custom instance of TelegramBotClient.
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBotCommandsStage WithBotClient(TelegramBotClient client); IBotCommandsStage WithBotClient(TelegramBotClient client, bool throwPendingUpdates = false, int timeoutInSeconds = 60);
/// <summary> /// <summary>
@@ -32,13 +35,15 @@ public interface INetworkingSelectionStage
/// </summary> /// </summary>
/// <param name="proxyHost"></param> /// <param name="proxyHost"></param>
/// <param name="Port"></param> /// <param name="Port"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBotCommandsStage WithHostAndPort(string proxyHost, int Port); IBotCommandsStage WithHostAndPort(string proxyHost, int Port, bool throwPendingUpdates = false, int timeoutInSeconds = 60);
/// <summary> /// <summary>
/// Uses a custom http client. /// Uses a custom http client.
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns> /// <returns></returns>
IBotCommandsStage WithHttpClient(HttpClient client); IBotCommandsStage WithHttpClient(HttpClient client, bool throwPendingUpdates = false, int timeoutInSeconds = 60);
} }
@@ -1,4 +1,5 @@
using TelegramBotBase.Interfaces; using System;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Builder.Interfaces; namespace TelegramBotBase.Builder.Interfaces;
@@ -22,15 +23,32 @@ public interface ISessionSerializationStage
/// Using the complex version of .Net JSON, which can serialize all objects. /// Using the complex version of .Net JSON, which can serialize all objects.
/// Saves in application directory. /// Saves in application directory.
/// </summary> /// </summary>
/// <remarks>
/// <para>
/// Has been changed lately to not use Newtonsoft.Json anymore.
/// For the legacy version add the nuget package below and use the <seealso cref="UseNewtonsoftJson"></seealso> method.
/// </para>
/// <seealso href="https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson/">TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson</seealso>
/// </remarks>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
/// <seealso cref="UseNewtonsoftJson"/>
ILanguageSelectionStage UseJSON(); ILanguageSelectionStage UseJSON();
/// <summary> /// <summary>
/// Using the complex version of .Net JSON, which can serialize all objects. /// Using the complex version of .Net JSON, which can serialize all objects.
/// Saves in application directory.
/// </summary> /// </summary>
/// <remarks>
/// <para>
/// Has been changed lately to not use Newtonsoft.Json anymore.
/// For the legacy version add the nuget package below and use the <seealso cref="UseNewtonsoftJson"></seealso> method.
/// </para>
/// <seealso href="https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson/">TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson</seealso>
/// </remarks>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
/// <seealso cref="UseNewtonsoftJson"/>
ILanguageSelectionStage UseJSON(string path); ILanguageSelectionStage UseJSON(string path);
/// <summary> /// <summary>
@@ -39,6 +57,7 @@ public interface ISessionSerializationStage
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
[Obsolete("Use UseJSON instead.")]
ILanguageSelectionStage UseSimpleJSON(); ILanguageSelectionStage UseSimpleJSON();
/// <summary> /// <summary>
@@ -46,6 +65,7 @@ public interface ISessionSerializationStage
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path"></param>
/// <returns></returns> /// <returns></returns>
[Obsolete("Use UseJSON instead.")]
ILanguageSelectionStage UseSimpleJSON(string path); ILanguageSelectionStage UseSimpleJSON(string path);
/// <summary> /// <summary>
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Builder.Interfaces
{
public interface IThreadingStage
{
/// <summary>
/// Uses one single thread for message loop. (Default)
/// </summary>
/// <returns></returns>
public IBuildingStage UseSingleThread();
/// <summary>
/// Using the threadpool for managing requests.
/// </summary>
/// <param name="workerThreads">Number of concurrent working threads.</param>
/// <param name="ioThreads">Number of concurrent I/O threads.</param>
/// <returns></returns>
public IBuildingStage UseThreadPool(int workerThreads = 2, int ioThreads = 1);
}
}
+12 -1
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Telegram.Bot.Types; using Telegram.Bot.Types;
@@ -20,6 +21,16 @@ public static class Extensions
scope = BotCommandScope.Default(); scope = BotCommandScope.Default();
} }
if (string.IsNullOrEmpty(command))
{
throw new ArgumentNullException(nameof(command), $"{nameof(command)} parameter can not be null or empty");
}
if(command.StartsWith(Constants.Telegram.BotCommandIndicator))
{
throw new ArgumentException($"{nameof(command)} parameter does not have to start with a slash, please remove.", $"{nameof(command)}");
}
var item = cmds.FirstOrDefault(a => a.Key.Type == scope.Type); var item = cmds.FirstOrDefault(a => a.Key.Type == scope.Type);
if (item.Value != null) if (item.Value != null)
+11
View File
@@ -16,4 +16,15 @@ public static class Telegram
public const int MaxReplyKeyboardCols = 12; public const int MaxReplyKeyboardCols = 12;
public const int MessageDeletionsPerSecond = 30; public const int MessageDeletionsPerSecond = 30;
/// <summary>
/// The maximum length of callback data. Will raise an exception of it exceeds it.
/// </summary>
public const int MaxCallBackDataBytes = 64;
/// <summary>
/// The slash constant which indicates a bot command.
/// </summary>
public const string BotCommandIndicator = "/";
} }
+36 -19
View File
@@ -51,7 +51,24 @@ public class ButtonGrid : ControlBase
DataSource = new ButtonFormDataSource(form); DataSource = new ButtonFormDataSource(form);
} }
public string Title { get; set; } = Default.Language["ButtonGrid_Title"]; string m_Title = Default.Language["ButtonGrid_Title"];
public string Title
{
get
{
return m_Title;
}
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException($"{nameof(Title)}", $"{nameof(Title)} property must have been a value unequal to null/empty");
}
m_Title = value;
}
}
public string ConfirmationText { get; set; } = ""; public string ConfirmationText { get; set; } = "";
@@ -340,14 +357,14 @@ public class ButtonGrid : ControlBase
} }
//var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText) //var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText); // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText);
// bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText) // bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText)
//var index = bf.FindRowByButton(button); //var index = bf.FindRowByButton(button);
check: check:
//Remove button click message //Remove button click message
if (DeleteReplyMessage) if (DeleteReplyMessage)
@@ -426,8 +443,6 @@ public class ButtonGrid : ControlBase
return; return;
} }
await result.ConfirmAction(ConfirmationText ?? "");
ButtonRow match = null; ButtonRow match = null;
var index = -1; var index = -1;
@@ -451,17 +466,19 @@ public class ButtonGrid : ControlBase
} }
//var bf = DataSource.ButtonForm; //var bf = DataSource.ButtonForm;
//var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) //var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData); // ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData);
//var index = bf.FindRowByButton(button); //var index = bf.FindRowByButton(button);
check: check:
if (match != null) if (match != null)
{ {
await result.ConfirmAction(ConfirmationText ?? "");
await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index, await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index,
match)); match));
@@ -506,13 +523,13 @@ public class ButtonGrid : ControlBase
if (DataSource.RowCount > Constants.Telegram.MaxInlineKeyBoardRows && !EnablePaging) if (DataSource.RowCount > Constants.Telegram.MaxInlineKeyBoardRows && !EnablePaging)
{ {
throw new MaximumRowsReachedException throw new MaximumRowsReachedException
{ Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxInlineKeyBoardRows }; { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxInlineKeyBoardRows };
} }
if (DataSource.ColumnCount > Constants.Telegram.MaxInlineKeyBoardCols) if (DataSource.ColumnCount > Constants.Telegram.MaxInlineKeyBoardCols)
{ {
throw new MaximumColsException throw new MaximumColsException
{ Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxInlineKeyBoardCols }; { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxInlineKeyBoardCols };
} }
break; break;
@@ -522,13 +539,13 @@ public class ButtonGrid : ControlBase
if (DataSource.RowCount > Constants.Telegram.MaxReplyKeyboardRows && !EnablePaging) if (DataSource.RowCount > Constants.Telegram.MaxReplyKeyboardRows && !EnablePaging)
{ {
throw new MaximumRowsReachedException throw new MaximumRowsReachedException
{ Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxReplyKeyboardRows }; { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxReplyKeyboardRows };
} }
if (DataSource.ColumnCount > Constants.Telegram.MaxReplyKeyboardCols) if (DataSource.ColumnCount > Constants.Telegram.MaxReplyKeyboardCols)
{ {
throw new MaximumColsException throw new MaximumColsException
{ Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxReplyKeyboardCols }; { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxReplyKeyboardCols };
} }
break; break;
@@ -693,7 +710,7 @@ public class ButtonGrid : ControlBase
Updated(); Updated();
} }
else else
//Remove event handler //Remove event handler
{ {
Device.MessageDeleted -= Device_MessageDeleted; Device.MessageDeleted -= Device_MessageDeleted;
} }
@@ -13,6 +13,7 @@ using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions; using TelegramBotBase.Exceptions;
using TelegramBotBase.Form; using TelegramBotBase.Form;
using TelegramBotBase.Localizations; using TelegramBotBase.Localizations;
using static System.Net.Mime.MediaTypeNames;
using static TelegramBotBase.Base.Async; using static TelegramBotBase.Base.Async;
namespace TelegramBotBase.Controls.Hybrid; namespace TelegramBotBase.Controls.Hybrid;
@@ -51,7 +52,24 @@ public class CheckedButtonList : ControlBase
DataSource = new ButtonFormDataSource(form); DataSource = new ButtonFormDataSource(form);
} }
public string Title { get; set; } = Default.Language["ButtonGrid_Title"]; string m_Title = Default.Language["ButtonGrid_Title"];
public string Title
{
get
{
return m_Title;
}
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException($"{nameof(Title)}", $"{nameof(Title)} property must have been a value unequal to null/empty");
}
m_Title = value;
}
}
public string ConfirmationText { get; set; } = ""; public string ConfirmationText { get; set; } = "";
@@ -29,8 +29,6 @@ public class TaggedButtonGrid : MultiView
public string BackLabel = Default.Language["ButtonGrid_Back"]; public string BackLabel = Default.Language["ButtonGrid_Back"];
public string CheckAllLabel = Default.Language["ButtonGrid_CheckAll"];
public string NextPageLabel = Default.Language["ButtonGrid_NextPage"]; public string NextPageLabel = Default.Language["ButtonGrid_NextPage"];
public string NoItemsLabel = Default.Language["ButtonGrid_NoItems"]; public string NoItemsLabel = Default.Language["ButtonGrid_NoItems"];
@@ -39,11 +37,17 @@ public class TaggedButtonGrid : MultiView
public string SearchLabel = Default.Language["ButtonGrid_SearchFeature"]; public string SearchLabel = Default.Language["ButtonGrid_SearchFeature"];
public string UncheckAllLabel = Default.Language["ButtonGrid_UncheckAll"]; public string TotalTagsLabel = Default.Language["TaggedButtonGrid_TotalTags"];
public string CheckedTagsLabel = Default.Language["TaggedButtonGrid_CheckedTags"];
public string CheckAllLabel = Default.Language["TaggedButtonGrid_CheckAll"];
public string UncheckAllLabel = Default.Language["TaggedButtonGrid_UncheckAll"];
public string SearchIcon = Default.Language["ButtonGrid_SearchIcon"]; public string SearchIcon = Default.Language["ButtonGrid_SearchIcon"];
public string TagIcon = Default.Language["ButtonGrid_TagIcon"]; public string TagIcon = Default.Language["TaggedButtonGrid_TagIcon"];
public TaggedButtonGrid() public TaggedButtonGrid()
{ {
@@ -63,7 +67,24 @@ public class TaggedButtonGrid : MultiView
DataSource = new ButtonFormDataSource(form); DataSource = new ButtonFormDataSource(form);
} }
public string Title { get; set; } = Default.Language["ButtonGrid_Title"]; string m_Title = Default.Language["ButtonGrid_Title"];
public string Title
{
get
{
return m_Title;
}
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException($"{nameof(Title)}", $"{nameof(Title)} property must have been a value unequal to null/empty");
}
m_Title = value;
}
}
public string ConfirmationText { get; set; } public string ConfirmationText { get; set; }
@@ -379,14 +400,6 @@ public class TaggedButtonGrid : MultiView
index = br.Item2; index = br.Item2;
} }
//var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText);
// bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText)
//var index = bf.FindRowByButton(button);
check: check:
@@ -490,10 +503,16 @@ public class TaggedButtonGrid : MultiView
if (result.MessageText == CheckAllLabel) if (result.MessageText == CheckAllLabel)
{ {
CheckAllTags(); CheckAllTags();
Updated();
result.Handled = true;
return;
} }
else if (result.MessageText == UncheckAllLabel) else if (result.MessageText == UncheckAllLabel)
{ {
UncheckAllTags(); UncheckAllTags();
Updated();
result.Handled = true;
return;
} }
var i = result.MessageText.LastIndexOf(" "); var i = result.MessageText.LastIndexOf(" ");
@@ -569,15 +588,6 @@ public class TaggedButtonGrid : MultiView
index = br.Item2; index = br.Item2;
} }
//var bf = DataSource.ButtonForm;
//var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData);
//var index = bf.FindRowByButton(button);
check: check:
if (match != null) if (match != null)
{ {
@@ -619,22 +629,47 @@ public class TaggedButtonGrid : MultiView
break; break;
case "$back$":
SelectedViewIndex = 0; default:
if (SelectedViewIndex != 1)
return;
switch (result.RawData)
{
case "$back$":
SelectedViewIndex = 0;
Updated();
return;
case "$checkall$":
CheckAllTags();
return;
case "$uncheckall$":
UncheckAllTags();
return;
}
if (SelectedTags.Contains(result.RawData))
{
SelectedTags.Remove(result.RawData);
}
else
{
SelectedTags.Add(result.RawData);
}
Updated(); Updated();
break;
case "$checkall$":
CheckAllTags();
break;
case "$uncheckall$":
UncheckAllTags();
break; break;
} }
@@ -723,8 +758,10 @@ public class TaggedButtonGrid : MultiView
if (EnableCheckAllTools) if (EnableCheckAllTools)
{ {
TagsSubHeadLayoutButtonRow = new ButtonRow(new ButtonBase(CheckAllLabel, "$checkall$"), TagsSubHeadLayoutButtonRow = new ButtonRow(new ButtonBase(string.Format(TotalTagsLabel, Tags.Count), "$total$"),
new ButtonBase(UncheckAllLabel, "$uncheckall$")); new ButtonBase(CheckAllLabel, "$checkall$"),
new ButtonBase(UncheckAllLabel, "$uncheckall$"),
new ButtonBase(string.Format(CheckedTagsLabel, SelectedTags.Count), "checked$"));
bf.AddButtonRow(TagsSubHeadLayoutButtonRow); bf.AddButtonRow(TagsSubHeadLayoutButtonRow);
} }
@@ -756,10 +793,6 @@ public class TaggedButtonGrid : MultiView
return; return;
} }
//if (bf.Count == 0)
// return;
var rkm = (ReplyKeyboardMarkup)bf; var rkm = (ReplyKeyboardMarkup)bf;
rkm.ResizeKeyboard = ResizeKeyboard; rkm.ResizeKeyboard = ResizeKeyboard;
rkm.OneTimeKeyboard = OneTimeKeyboard; rkm.OneTimeKeyboard = OneTimeKeyboard;
@@ -881,21 +914,20 @@ public class TaggedButtonGrid : MultiView
{ {
Message m = null; Message m = null;
var form = DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage, ButtonForm form = null;
EnableSearch ? SearchQuery : null);
//if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{
// form = form.FilterDuplicate(this.SearchQuery, true);
//}
//else
//{
// form = form.Duplicate();
//}
if (Tags != null && SelectedTags != null) if (Tags != null && SelectedTags != null)
{ {
form = DataSource.PickAllItems(EnableSearch ? SearchQuery : null); //CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage,
form = form.TagDuplicate(SelectedTags); form = form.TagDuplicate(SelectedTags);
form = new ButtonForm(form.ToRowList().Skip(CurrentPageIndex * ItemRowsPerPage).Take(ItemRowsPerPage));
}
else
{
form = DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage,
EnableSearch ? SearchQuery : null);
} }
if (EnablePaging) if (EnablePaging)
+7 -1
View File
@@ -16,7 +16,7 @@ public class Label : ControlBase
{ {
private bool _renderNecessary = true; private bool _renderNecessary = true;
private string _text = string.Empty; private string _text = Default.Language["Label_Text"];
public String Text public String Text
{ {
@@ -29,6 +29,10 @@ public class Label : ControlBase
if (_text == value) if (_text == value)
return; return;
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException($"{nameof(Text)}", $"{nameof(Text)} property must have been a value unequal to null/empty");
}
_text = value; _text = value;
_renderNecessary = true; _renderNecessary = true;
@@ -36,6 +40,8 @@ public class Label : ControlBase
} }
} }
private ParseMode _parseMode = ParseMode.Markdown; private ParseMode _parseMode = ParseMode.Markdown;
public ParseMode ParseMode public ParseMode ParseMode
@@ -32,10 +32,32 @@ public class MultiToggleButton : ControlBase
/// </summary> /// </summary>
public string ChangedString { get; set; } = Default.Language["MultiToggleButton_Changed"]; public string ChangedString { get; set; } = Default.Language["MultiToggleButton_Changed"];
private string _title = Default.Language["MultiToggleButton_Title"];
/// <summary> /// <summary>
/// This holds the title of the control. /// This holds the title of the control.
/// </summary> /// </summary>
public string Title { get; set; } = Default.Language["MultiToggleButton_Title"]; public String Title
{
get
{
return _title;
}
set
{
if (_title == value)
return;
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException($"{nameof(Title)}", $"{nameof(Title)} must have been a value unequal to null/empty");
}
_title = value;
_renderNecessary = true;
}
}
public int? MessageId { get; set; } public int? MessageId { get; set; }
@@ -36,7 +36,31 @@ public class ToggleButton : ControlBase
public string ChangedString { get; set; } = Default.Language["ToggleButton_Changed"]; public string ChangedString { get; set; } = Default.Language["ToggleButton_Changed"];
public string Title { get; set; } = Default.Language["ToggleButton_Title"]; private string _title = Default.Language["ToggleButton_Title"];
public String Title
{
get
{
return _title;
}
set
{
if (_title == value)
return;
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException($"{nameof(Title)}", $"{nameof(Title)} property must have been a value unequal to null/empty");
}
_title = value;
_renderNecessary = true;
}
}
public int? MessageId { get; set; } public int? MessageId { get; set; }
@@ -0,0 +1,86 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Form;
namespace TelegramBotBase.DependencyInjection
{
public static class Extensions
{
internal static FieldInfo _ServiceProviderField = typeof(FormBase).GetField("_serviceProvider", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
/// <summary>
/// Use Dependency Injection to create new form and inject parameters. (Main variant)
/// </summary>
/// <typeparam name="NewForm"></typeparam>
/// <param name="args"></param>
/// <returns></returns>
public static async Task<NewForm> NavigateTo<NewForm>(this FormBase current_form, params object[] args)
where NewForm : FormBase
{
var _serviceProvider = current_form.GetServiceProvider();
var instance = ActivatorUtilities.CreateInstance(_serviceProvider, typeof(NewForm)) as NewForm;
if (instance == null)
return null; //throw new Exception("Could not instantiate new form via DI.");
instance.SetServiceProvider(_serviceProvider);
await current_form.NavigateTo(instance, args);
return instance;
}
/// <summary>
/// Use Dependency Injection to create new form and inject parameters. (Alternative variant)
/// </summary>
/// <param name="current_form"></param>
/// <param name="formBaseType"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static async Task<FormBase> NavigateTo(this FormBase current_form, Type formBaseType, params object[] args)
{
if (!typeof(FormBase).IsAssignableFrom(formBaseType))
throw new ArgumentException($"{nameof(formBaseType)} argument must be a {nameof(FormBase)} type");
var _serviceProvider = current_form.GetServiceProvider();
var instance = ActivatorUtilities.CreateInstance(_serviceProvider, formBaseType) as FormBase;
if (instance == null)
return null; //throw new Exception("Could not instantiate new form via DI.");
instance.SetServiceProvider(_serviceProvider);
await current_form.NavigateTo(instance, args);
return instance;
}
/// <summary>
/// Sets the internal service provider field.
/// </summary>
/// <param name="form"></param>
/// <param name="serviceProvider"></param>
public static void SetServiceProvider(this FormBase form, IServiceProvider serviceProvider)
{
_ServiceProviderField?.SetValue(form, serviceProvider);
}
/// <summary>
/// Gets the internal service provider field value.
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public static IServiceProvider GetServiceProvider(this FormBase form)
{
var sp = _ServiceProviderField?.GetValue(form) as IServiceProvider;
return sp;
}
}
}
@@ -0,0 +1,35 @@
using System;
using Telegram.Bot.Exceptions;
namespace TelegramBotBase.Exceptions;
public sealed class CallbackDataTooLongException : Exception
{
static ApiRequestException _innerException = new Telegram.Bot.Exceptions.ApiRequestException("Bad Request: BUTTON_DATA_INVALID", 400);
static String _message = $"You have exceeded the maximum {Constants.Telegram.MaxCallBackDataBytes} bytes of callback data.\r\nThis is a pre-sending message from the TelegramBotBase framework.\r\nread more: https://core.telegram.org/bots/api#inlinekeyboardbutton";
static String _message_with_bytes = $"You have exceeded the maximum {Constants.Telegram.MaxCallBackDataBytes} bytes of callback data with @current_callback_bytes@ bytes.\r\nThis is a pre-sending message from the TelegramBotBase framework.\r\nread more: https://core.telegram.org/bots/api#inlinekeyboardbutton";
public CallbackDataTooLongException() : base(_message, _innerException)
{
}
/// <summary>
///
/// </summary>
/// <param name="current_callback_bytes">The amount of callback bytes generated.</param>
public CallbackDataTooLongException(int current_callback_bytes) : base(getMessage(current_callback_bytes), _innerException)
{
}
static String getMessage(int current_callback_bytes = -1)
{
if (current_callback_bytes == -1)
return _message;
return _message_with_bytes.Replace("@current_callback_bytes@", current_callback_bytes.ToString());
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Exceptions
{
public sealed class InvalidServiceProviderConfiguration : Exception
{
public InvalidServiceProviderConfiguration(string message, Exception innerException) : base(message, innerException) { }
}
}
@@ -12,7 +12,7 @@ public class DefaultStartFormFactory : IStartFormFactory
{ {
if (!typeof(FormBase).IsAssignableFrom(startFormClass)) if (!typeof(FormBase).IsAssignableFrom(startFormClass))
{ {
throw new ArgumentException("startFormClass argument must be a FormBase type"); throw new ArgumentException($"{nameof(startFormClass)} argument must be a {nameof(FormBase)} type");
} }
_startFormClass = startFormClass; _startFormClass = startFormClass;
@@ -1,5 +1,7 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using TelegramBotBase.DependencyInjection;
using TelegramBotBase.Exceptions;
using TelegramBotBase.Form; using TelegramBotBase.Form;
using TelegramBotBase.Interfaces; using TelegramBotBase.Interfaces;
@@ -14,16 +16,30 @@ public class ServiceProviderStartFormFactory : IStartFormFactory
{ {
if (!typeof(FormBase).IsAssignableFrom(startFormClass)) if (!typeof(FormBase).IsAssignableFrom(startFormClass))
{ {
throw new ArgumentException("startFormClass argument must be a FormBase type"); throw new ArgumentException($"{nameof(startFormClass)} argument must be a {nameof(FormBase)} type");
} }
_startFormClass = startFormClass; _startFormClass = startFormClass;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public FormBase CreateForm() public FormBase CreateForm()
{ {
return (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass); FormBase fb = null;
try
{
fb = (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass);
}
catch(InvalidOperationException ex)
{
throw new InvalidServiceProviderConfiguration(ex.Message, ex);
}
//Sets an internal field for future ServiceProvider navigation
fb.SetServiceProvider(_serviceProvider);
return fb;
} }
} }
+10
View File
@@ -23,6 +23,11 @@ public class ButtonForm
DependencyControl = control; DependencyControl = control;
} }
public ButtonForm(IEnumerable<ButtonRow> rows)
{
_buttons = rows.ToList();
}
public IReplyMarkup Markup { get; set; } public IReplyMarkup Markup { get; set; }
@@ -149,6 +154,11 @@ public class ButtonForm
.Aggregate((a, b) => a.Union(b).ToList()); .Aggregate((a, b) => a.Union(b).ToList());
} }
public List<ButtonRow> ToRowList()
{
return _buttons;
}
public InlineKeyboardButton[][] ToInlineButtonArray() public InlineKeyboardButton[][] ToInlineButtonArray()
{ {
var ikb = _buttons.Select(a => a.ToArray().Select(b => b.ToInlineButton(this)).ToArray()).ToArray(); var ikb = _buttons.Select(a => a.ToArray().Select(b => b.ToInlineButton(this)).ToArray()).ToArray();
+20 -25
View File
@@ -1,14 +1,18 @@
using Newtonsoft.Json; using System.Text.Json;
using System.Text;
using TelegramBotBase.Exceptions;
using System.Text.Json.Serialization;
namespace TelegramBotBase.Form; namespace TelegramBotBase.Form;
/// <summary> /// <summary>
/// Base class for serializing buttons and data /// Base class for serializing buttons and data
/// </summary> /// </summary>
public class CallbackData public class CallbackData
{ {
public CallbackData() public CallbackData()
{ {
} }
public CallbackData(string method, string value) public CallbackData(string method, string value)
@@ -17,28 +21,30 @@ public class CallbackData
Value = value; Value = value;
} }
[JsonProperty("m")] public string Method { get; set; } [JsonPropertyName("m")] public string Method { get; set; }
[JsonProperty("v")] public string Value { get; set; } [JsonPropertyName("v")] public string Value { get; set; }
public static string Create(string method, string value) public static string Create(string method, string value)
{ {
return new CallbackData(method, value).Serialize(); return new CallbackData(method, value).Serialize(true);
} }
/// <summary> /// <summary>
/// Serializes data to json string /// Serializes data to json string
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public string Serialize() public string Serialize(bool throwExceptionOnOverflow = false)
{ {
var s = ""; var s = string.Empty;
try
{ s = JsonSerializer.Serialize(this);
s = JsonConvert.SerializeObject(this);
} //Is data over 64 bytes ?
catch int byte_count = Encoding.UTF8.GetByteCount(s);
if (throwExceptionOnOverflow && byte_count > Constants.Telegram.MaxCallBackDataBytes)
{ {
throw new CallbackDataTooLongException(byte_count);
} }
return s; return s;
@@ -51,19 +57,8 @@ public class CallbackData
/// <returns></returns> /// <returns></returns>
public static CallbackData Deserialize(string data) public static CallbackData Deserialize(string data)
{ {
CallbackData cd = null; return JsonSerializer.Deserialize<CallbackData>(data);
try
{
cd = JsonConvert.DeserializeObject<CallbackData>(data);
return cd;
}
catch
{
}
return null;
} }
public static implicit operator string(CallbackData callbackData) => callbackData.Serialize(); public static implicit operator string(CallbackData callbackData) => callbackData.Serialize(true);
} }
+44 -1
View File
@@ -1,4 +1,5 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
@@ -7,6 +8,8 @@ namespace TelegramBotBase.Form;
public class GroupForm : FormBase public class GroupForm : FormBase
{ {
//Prior V21
[Obsolete("Check Telegram.Bot nuget package changes.")]
public override async Task Load(MessageResult message) public override async Task Load(MessageResult message)
{ {
switch (message.MessageType) switch (message.MessageType)
@@ -46,6 +49,46 @@ public class GroupForm : FormBase
} }
} }
//Past V21
//public override async Task Load(MessageResult message)
//{
// switch (message.MessageType)
// {
// case MessageType.NewChatMembers:
// await OnMemberChanges(new MemberChangeEventArgs(MessageType.NewChatMembers, message,
// message.Message.NewChatMembers));
// break;
// case MessageType.LeftChatMember:
// await OnMemberChanges(new MemberChangeEventArgs(MessageType.LeftChatMember, message,
// message.Message.LeftChatMember));
// break;
// case MessageType.NewChatPhoto:
// case MessageType.DeleteChatPhoto:
// case MessageType.NewChatTitle:
// case MessageType.MigrateFromChatId:
// case MessageType.MigrateToChatId:
// case MessageType.PinnedMessage:
// case MessageType.GroupChatCreated:
// case MessageType.SupergroupChatCreated:
// case MessageType.ChannelChatCreated:
// await OnGroupChanged(new GroupChangedEventArgs(message.MessageType, message));
// break;
// default:
// await OnMessage(message);
// break;
// }
//}
public override async Task Edited(MessageResult message) public override async Task Edited(MessageResult message)
{ {
await OnMessageEdit(message); await OnMessageEdit(message);
+1 -1
View File
@@ -82,7 +82,7 @@ public class PromptDialog : ModalDialog
{ {
var bf = new ButtonForm(); var bf = new ButtonForm();
bf.AddButtonRow(new ButtonBase(BackLabel, "back")); bf.AddButtonRow(new ButtonBase(BackLabel, "back"));
await Device.Send(Message, (ReplyMarkupBase)bf); await Device.Send(Message, (IReplyMarkup)bf);
return; return;
} }
+17 -1
View File
@@ -1,10 +1,24 @@
using System; using System;
using System.Threading.Tasks;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types;
using TelegramBotBase.Form; using TelegramBotBase.Form;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using Telegram.Bot;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Interfaces; namespace TelegramBotBase.Interfaces;
internal interface IDeviceSession public interface IDeviceSession : IDeviceSessionMethods
{ {
MessageClient Client => ActiveForm.Client;
int LastMessageId => LastMessage?.MessageId ?? -1;
Message LastMessage { get; set; }
/// <summary> /// <summary>
/// Device or chat id /// Device or chat id
/// </summary> /// </summary>
@@ -35,4 +49,6 @@ internal interface IDeviceSession
/// contains if the form has been switched (navigated) /// contains if the form has been switched (navigated)
/// </summary> /// </summary>
bool FormSwitched { get; set; } bool FormSwitched { get; set; }
} }
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
using Telegram.Bot.Types;
using TelegramBotBase.Form;
using Telegram.Bot;
using TelegramBotBase.Base;
using TelegramBotBase.Args;
namespace TelegramBotBase.Interfaces
{
public interface IDeviceSessionMethods
{
string GetChatTitle();
Task BanUser(long userId, DateTime until = default);
Task UnbanUser(long userId);
Task ChangeChatPermissions(ChatPermissions permissions);
Task RestrictUser(long userId, ChatPermissions permissions, bool? useIndependentGroupPermission = null, DateTime until = default);
Task ConfirmAction(string callbackQueryId, string message = "", bool showAlert = false,
string urlToOpen = null);
Task<bool> DeleteMessage(int messageId = -1);
Task<bool> DeleteMessage(Message message);
Task<Message> HideReplyKeyboard(string closedMsg = "Closed", bool autoDeleteResponse = true);
Task<Message> Send(string text, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown,
bool markdownV2AutoEscape = true);
Task<Message> Send(string text, IReplyMarkup markup, int replyTo = 0,
bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown,
bool markdownV2AutoEscape = true);
Task<Message> Send(string text, InlineKeyboardMarkup markup, int replyTo = 0,
bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown,
bool markdownV2AutoEscape = true);
Task SetAction(ChatAction action);
Task<Message> SendTextFile(string filename, string textcontent, Encoding encoding = null,
string caption = "", ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false);
Task<Message> SendDocument(InputFile document, string caption = "",
ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false);
Task<Message> SendPhoto(InputFile file, string caption = null, ButtonForm buttons = null,
int replyTo = 0, bool disableNotification = false,
ParseMode parseMode = ParseMode.Markdown);
Task<Message> SendVideo(InputFile file, string caption = null, ButtonForm buttons = null,
int replyTo = 0, bool disableNotification = false,
ParseMode parseMode = ParseMode.Markdown);
Task<Message> SendVideo(string url, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown);
Task<Message> SendVideo(string filename, byte[] video, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown);
Task<Message> SendLocalVideo(string filepath, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false,
ParseMode parseMode = ParseMode.Markdown);
Task<Message> Edit(int messageId, string text, ButtonForm buttons = null,
ParseMode parseMode = ParseMode.Markdown);
Task<Message> Edit(int messageId, string text, InlineKeyboardMarkup markup,
ParseMode parseMode = ParseMode.Markdown);
Task<Message> Edit(Message message, ButtonForm buttons = null,
ParseMode parseMode = ParseMode.Markdown);
Task<Message> EditReplyMarkup(int messageId, ButtonForm bf);
Task<Message> RequestContact(string buttonText = "Send your contact",
string requestMessage = "Give me your phone number!",
bool oneTimeOnly = true);
Task<Message> RequestLocation(string buttonText = "Send your location",
string requestMessage = "Give me your location!",
bool oneTimeOnly = true);
Task<ChatMember> GetChatUser(long userId);
event Async.AsyncEventHandler<MessageSentEventArgs> MessageSent;
event EventHandler<MessageReceivedEventArgs> MessageReceived;
event EventHandler<MessageDeletedEventArgs> MessageDeleted;
Task Api(Func<ITelegramBotClient, Task> call);
Task<T> Api<T>(Func<ITelegramBotClient, Task<T>> call);
T Raw<T>(Func<ITelegramBotClient, T> call);
}
}
@@ -2,13 +2,12 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Interfaces; namespace TelegramBotBase.Interfaces;
public interface IMessageLoopFactory public interface IMessageLoopFactory
{ {
Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult e); Task MessageLoop(BotBase bot, IDeviceSession session, UpdateResult ur, MessageResult e);
event EventHandler<UnhandledCallEventArgs> UnhandledCall; event EventHandler<UnhandledCallEventArgs> UnhandledCall;
} }
+7 -4
View File
@@ -1,6 +1,6 @@
namespace TelegramBotBase.Localizations; namespace TelegramBotBase.Localizations;
public class English : Localization public sealed class English : Localization
{ {
public English() public English()
{ {
@@ -12,8 +12,10 @@ public class English : Localization
Values["ButtonGrid_CurrentPage"] = "Page {0} of {1}"; Values["ButtonGrid_CurrentPage"] = "Page {0} of {1}";
Values["ButtonGrid_SearchFeature"] = "💡 Send a message to filter the list. Click the 🔍 to reset the filter."; Values["ButtonGrid_SearchFeature"] = "💡 Send a message to filter the list. Click the 🔍 to reset the filter.";
Values["ButtonGrid_Back"] = "Back"; Values["ButtonGrid_Back"] = "Back";
Values["ButtonGrid_CheckAll"] = "Check all"; Values["TaggedButtonGrid_TotalTags"] = "Total: {0}";
Values["ButtonGrid_UncheckAll"] = "Uncheck all"; Values["TaggedButtonGrid_CheckedTags"] = "Checked: {0}";
Values["TaggedButtonGrid_CheckAll"] = "Check all";
Values["TaggedButtonGrid_UncheckAll"] = "Uncheck all";
Values["CalendarPicker_Title"] = "Pick date"; Values["CalendarPicker_Title"] = "Pick date";
Values["CalendarPicker_PreviousPage"] = "◀️"; Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️"; Values["CalendarPicker_NextPage"] = "▶️";
@@ -31,6 +33,7 @@ public class English : Localization
Values["PromptDialog_Back"] = "Back"; Values["PromptDialog_Back"] = "Back";
Values["ToggleButton_Changed"] = "Setting changed"; Values["ToggleButton_Changed"] = "Setting changed";
Values["ButtonGrid_SearchIcon"] = "🔍"; Values["ButtonGrid_SearchIcon"] = "🔍";
Values["ButtonGrid_TagIcon"] = "📁"; Values["TaggedButtonGrid_TagIcon"] = "📁";
Values["Label_Text"] = "Default label text";
} }
} }
+9 -5
View File
@@ -1,6 +1,6 @@
namespace TelegramBotBase.Localizations; namespace TelegramBotBase.Localizations;
public class German : Localization public sealed class German : Localization
{ {
public German() public German()
{ {
@@ -10,11 +10,12 @@ public class German : Localization
Values["ButtonGrid_PreviousPage"] = "◀️"; Values["ButtonGrid_PreviousPage"] = "◀️";
Values["ButtonGrid_NextPage"] = "▶️"; Values["ButtonGrid_NextPage"] = "▶️";
Values["ButtonGrid_CurrentPage"] = "Seite {0} von {1}"; Values["ButtonGrid_CurrentPage"] = "Seite {0} von {1}";
Values["ButtonGrid_SearchFeature"] = Values["ButtonGrid_SearchFeature"] = "💡 Sende eine Nachricht um die Liste zu filtern. Klicke die 🔍 um den Filter zurückzusetzen.";
"💡 Sende eine Nachricht um die Liste zu filtern. Klicke die 🔍 um den Filter zurückzusetzen.";
Values["ButtonGrid_Back"] = "Zurück"; Values["ButtonGrid_Back"] = "Zurück";
Values["ButtonGrid_CheckAll"] = "Alle auswählen"; Values["TaggedButtonGrid_TotalTags"] = "Gesamt: {0}";
Values["ButtonGrid_UncheckAll"] = "Keine auswählen"; Values["TaggedButtonGrid_CheckedTags"] = "Ausgewählt: {0}";
Values["TaggedButtonGrid_CheckAll"] = "Alle auswählen";
Values["TaggedButtonGrid_UncheckAll"] = "Keine auswählen";
Values["CalendarPicker_Title"] = "Datum auswählen"; Values["CalendarPicker_Title"] = "Datum auswählen";
Values["CalendarPicker_PreviousPage"] = "◀️"; Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️"; Values["CalendarPicker_NextPage"] = "▶️";
@@ -31,5 +32,8 @@ public class German : Localization
Values["MultiToggleButton_Changed"] = "Ausgewählt"; Values["MultiToggleButton_Changed"] = "Ausgewählt";
Values["PromptDialog_Back"] = "Zurück"; Values["PromptDialog_Back"] = "Zurück";
Values["ToggleButton_Changed"] = "Einstellung geändert"; Values["ToggleButton_Changed"] = "Einstellung geändert";
Values["ButtonGrid_SearchIcon"] = "🔍";
Values["TaggedButtonGrid_TagIcon"] = "📁";
Values["Label_Text"] = "Standard Label Text";
} }
} }
+40
View File
@@ -0,0 +1,40 @@
namespace TelegramBotBase.Localizations;
public sealed class Persian : Localization
{
public Persian()
{
Values["Language"] = "فارسی";
Values["ButtonGrid_Title"] = "منو";
Values["ButtonGrid_NoItems"] = "هیچ آیتمی وجود ندارد.";
Values["ButtonGrid_PreviousPage"] = "◀️";
Values["ButtonGrid_NextPage"] = "▶️";
Values["ButtonGrid_CurrentPage"] = "صفحه ی {0} از {1}";
Values["ButtonGrid_SearchFeature"] = "💡 برای فیلتر کردن لیست پیام ارسال کنید. برای بازنشانی فیلتر روی 🔍 کلیک کنید.";
Values["ButtonGrid_Back"] = "بازگشت";
Values["TaggedButtonGrid_TotalTags"] = "Total: {0}";
Values["TaggedButtonGrid_CheckedTags"] = "Checked: {0}";
Values["TaggedButtonGrid_CheckAll"] = "بررسی کردن همه";
Values["TaggedButtonGrid_UncheckAll"] = "بررسی نکردن همه";
Values["CalendarPicker_Title"] = "تاریخ را انتخاب کنید";
Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️";
Values["TreeView_Title"] = "گره را انتخاب کنید";
Values["TreeView_LevelUp"] = "🔼 سطح بالا";
Values["ToggleButton_On"] = "روشن";
Values["ToggleButton_Off"] = "خاموش";
Values["ToggleButton_OnIcon"] = "⚫";
Values["ToggleButton_OffIcon"] = "⚪";
Values["ToggleButton_Title"] = "تغییر وضعیت";
Values["ToggleButton_Changed"] = "انتخاب شده";
Values["MultiToggleButton_SelectedIcon"] = "✅";
Values["MultiToggleButton_Title"] = "چند تعویض";
Values["MultiToggleButton_Changed"] = "انتخاب شده";
Values["PromptDialog_Back"] = "بازگشت";
Values["ToggleButton_Changed"] = "تنظیمات تغییر کرد";
Values["ButtonGrid_SearchIcon"] = "🔍";
Values["TaggedButtonGrid_TagIcon"] = "📁";
Values["Label_Text"] = "متن برچسب پیش‌فرض";
}
}
+39
View File
@@ -0,0 +1,39 @@
namespace TelegramBotBase.Localizations;
public sealed class Russian : Localization
{
public Russian()
{
Values["Language"] = "Русский (Russian)";
Values["ButtonGrid_Title"] = "Меню";
Values["ButtonGrid_NoItems"] = "Нет доступных элементов.";
Values["ButtonGrid_PreviousPage"] = "◀️";
Values["ButtonGrid_NextPage"] = "▶️";
Values["ButtonGrid_CurrentPage"] = "Страница {0} из {1}";
Values["ButtonGrid_SearchFeature"] = "💡 Отправьте сообщение, чтобы отфильтровать список. Нажмите на 🔍, чтобы сбросить фильтр.";
Values["ButtonGrid_Back"] = "Назад";
Values["TaggedButtonGrid_TotalTags"] = "Всего: {0}";
Values["TaggedButtonGrid_CheckedTags"] = "Отмечено: {0}";
Values["TaggedButtonGrid_CheckAll"] = "Выделить все";
Values["TaggedButtonGrid_UncheckAll"] = "Отменить выбор";
Values["CalendarPicker_Title"] = "Календарь / Выберите дату";
Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️";
Values["TreeView_Title"] = "Выберите пункт";
Values["TreeView_LevelUp"] = "🔼 Обратно";
Values["ToggleButton_On"] = "Вкл";
Values["ToggleButton_Off"] = "Выкл";
Values["ToggleButton_OnIcon"] = "⚫";
Values["ToggleButton_OffIcon"] = "⚪";
Values["ToggleButton_Title"] = "Переключить";
Values["ToggleButton_Changed"] = "Выбрано";
Values["MultiToggleButton_SelectedIcon"] = "✅";
Values["MultiToggleButton_Title"] = "Множественный выбор";
Values["MultiToggleButton_Changed"] = "Выбрано";
Values["PromptDialog_Back"] = "Назад";
Values["ToggleButton_Changed"] = "Настройки изменены";
Values["ButtonGrid_SearchIcon"] = "🔍";
Values["TaggedButtonGrid_TagIcon"] = "📁";
Values["Label_Text"] = "Текст метки по умолчанию";
}
}
@@ -5,7 +5,6 @@ using Telegram.Bot.Types.Enums;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Interfaces; using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.MessageLoops; namespace TelegramBotBase.MessageLoops;
@@ -18,7 +17,7 @@ public class FormBaseMessageLoop : IMessageLoopFactory
private readonly EventHandlerList _events = new(); private readonly EventHandlerList _events = new();
public async Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult mr) public async Task MessageLoop(BotBase bot, IDeviceSession session, UpdateResult ur, MessageResult mr)
{ {
var update = ur.RawData; var update = ur.RawData;
@@ -72,14 +71,23 @@ public class FormBaseMessageLoop : IMessageLoopFactory
} }
} }
//Message edited ?
if(update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event //Action Event
if (!session.FormSwitched && mr.IsAction) if (!session.FormSwitched && mr.IsAction && !mr.Handled)
{ {
//Send Action event to controls //Send Action event to controls
await activeForm.ActionControls(mr); await activeForm.ActionControls(mr);
//Send Action event to form itself if (!mr.Handled)
await activeForm.Action(mr); {
//Send Action event to form itself, if not already handled by a control
await activeForm.Action(mr);
}
if (!mr.Handled) if (!mr.Handled)
{ {
@@ -5,7 +5,6 @@ using Telegram.Bot.Types.Enums;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Interfaces; using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.MessageLoops; namespace TelegramBotBase.MessageLoops;
@@ -18,7 +17,7 @@ public class FullMessageLoop : IMessageLoopFactory
private readonly EventHandlerList _events = new(); private readonly EventHandlerList _events = new();
public async Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult mr) public async Task MessageLoop(BotBase bot, IDeviceSession session, UpdateResult ur, MessageResult mr)
{ {
var update = ur.RawData; var update = ur.RawData;
@@ -65,14 +64,23 @@ public class FullMessageLoop : IMessageLoopFactory
} }
} }
//Message edited ?
if (update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event //Action Event
if (!session.FormSwitched && mr.IsAction) if (!session.FormSwitched && mr.IsAction && !mr.Handled)
{ {
//Send Action event to controls //Send Action event to controls
await activeForm.ActionControls(mr); await activeForm.ActionControls(mr);
//Send Action event to form itself if (!mr.Handled)
await activeForm.Action(mr); {
//Send Action event to form itself, if not already handled by a control
await activeForm.Action(mr);
}
if (!mr.Handled) if (!mr.Handled)
{ {
@@ -4,7 +4,6 @@ using System.Threading.Tasks;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Interfaces; using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.MessageLoops; namespace TelegramBotBase.MessageLoops;
@@ -17,7 +16,7 @@ public class MinimalMessageLoop : IMessageLoopFactory
private readonly EventHandlerList _events = new(); private readonly EventHandlerList _events = new();
public async Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult mr) public async Task MessageLoop(BotBase bot, IDeviceSession session, UpdateResult ur, MessageResult mr)
{ {
var update = ur.RawData; var update = ur.RawData;
+6 -6
View File
@@ -21,7 +21,7 @@ public class SessionManager
public SessionManager(BotBase botBase) public SessionManager(BotBase botBase)
{ {
BotBase = botBase; BotBase = botBase;
SessionList = new Dictionary<long, DeviceSession>(); SessionList = new Dictionary<long, IDeviceSession>();
} }
/// <summary> /// <summary>
@@ -32,7 +32,7 @@ public class SessionManager
/// <summary> /// <summary>
/// A list of all active sessions. /// A list of all active sessions.
/// </summary> /// </summary>
public Dictionary<long, DeviceSession> SessionList { get; set; } public Dictionary<long, IDeviceSession> SessionList { get; set; }
/// <summary> /// <summary>
@@ -45,7 +45,7 @@ public class SessionManager
/// </summary> /// </summary>
/// <param name="deviceId"></param> /// <param name="deviceId"></param>
/// <returns></returns> /// <returns></returns>
public DeviceSession GetSession(long deviceId) public IDeviceSession GetSession(long deviceId)
{ {
var ds = SessionList.FirstOrDefault(a => a.Key == deviceId).Value ?? null; var ds = SessionList.FirstOrDefault(a => a.Key == deviceId).Value ?? null;
return ds; return ds;
@@ -57,7 +57,7 @@ public class SessionManager
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="deviceId"></param> /// <param name="deviceId"></param>
/// <returns></returns> /// <returns></returns>
public async Task<DeviceSession> StartSession(long deviceId) public async Task<IDeviceSession> StartSession(long deviceId)
{ {
var start = BotBase.StartFormFactory.CreateForm(); var start = BotBase.StartFormFactory.CreateForm();
@@ -91,7 +91,7 @@ public class SessionManager
/// Returns all active User Sessions. /// Returns all active User Sessions.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public List<DeviceSession> GetUserSessions() public List<IDeviceSession> GetUserSessions()
{ {
return SessionList.Where(a => a.Key > 0).Select(a => a.Value).ToList(); return SessionList.Where(a => a.Key > 0).Select(a => a.Value).ToList();
} }
@@ -100,7 +100,7 @@ public class SessionManager
/// Returns all active Group Sessions. /// Returns all active Group Sessions.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public List<DeviceSession> GetGroupSessions() public List<IDeviceSession> GetGroupSessions()
{ {
return SessionList.Where(a => a.Key < 0).Select(a => a.Value).ToList(); return SessionList.Where(a => a.Key < 0).Select(a => a.Value).ToList();
} }
+98 -209
View File
@@ -131,13 +131,9 @@ public class DeviceSession : IDeviceSession
public async Task ConfirmAction(string callbackQueryId, string message = "", bool showAlert = false, public async Task ConfirmAction(string callbackQueryId, string message = "", bool showAlert = false,
string urlToOpen = null) string urlToOpen = null)
{ {
try
{ await Client.TelegramClient.AnswerCallbackQueryAsync(callbackQueryId, message, showAlert, urlToOpen);
await Client.TelegramClient.AnswerCallbackQueryAsync(callbackQueryId, message, showAlert, urlToOpen);
}
catch
{
}
} }
/// <summary> /// <summary>
@@ -157,17 +153,8 @@ public class DeviceSession : IDeviceSession
throw new MessageTooLongException(text.Length); throw new MessageTooLongException(text.Length);
} }
try
{
return await Api(a =>
a.EditMessageTextAsync(DeviceId, messageId, text, parseMode, replyMarkup: markup));
}
catch
{
}
return await Api(a => a.EditMessageTextAsync(DeviceId, messageId, text, parseMode, replyMarkup: markup));
return null;
} }
/// <summary> /// <summary>
@@ -185,17 +172,9 @@ public class DeviceSession : IDeviceSession
throw new MessageTooLongException(text.Length); throw new MessageTooLongException(text.Length);
} }
try
{
return await Api(a =>
a.EditMessageTextAsync(DeviceId, messageId, text, parseMode, replyMarkup: markup));
}
catch
{
}
return await Api(a => a.EditMessageTextAsync(DeviceId, messageId, text, parseMode, replyMarkup: markup));
return null;
} }
/// <summary> /// <summary>
@@ -215,18 +194,8 @@ public class DeviceSession : IDeviceSession
throw new MessageTooLongException(message.Text.Length); throw new MessageTooLongException(message.Text.Length);
} }
try return await Api(a => a.EditMessageTextAsync(DeviceId, message.MessageId, message.Text, parseMode,
{ replyMarkup: markup));
return await Api(a =>
a.EditMessageTextAsync(DeviceId, message.MessageId, message.Text, parseMode,
replyMarkup: markup));
}
catch
{
}
return null;
} }
/// <summary> /// <summary>
@@ -237,15 +206,8 @@ public class DeviceSession : IDeviceSession
/// <returns></returns> /// <returns></returns>
public async Task<Message> EditReplyMarkup(int messageId, ButtonForm bf) public async Task<Message> EditReplyMarkup(int messageId, ButtonForm bf)
{ {
try
{
return await Api(a => a.EditMessageReplyMarkupAsync(DeviceId, messageId, bf));
}
catch
{
}
return null; return await Api(a => a.EditMessageReplyMarkupAsync(DeviceId, messageId, bf));
} }
/// <summary> /// <summary>
@@ -277,21 +239,16 @@ public class DeviceSession : IDeviceSession
text = text.MarkdownV2Escape(); text = text.MarkdownV2Escape();
} }
try
{
var t = Api(a => a.SendTextMessageAsync(deviceId, text, null, parseMode, replyToMessageId: replyTo,
replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendTextMessageAsync(deviceId, text, null, parseMode, replyToMessageId: replyTo,
replyMarkup: markup, disableNotification: disableNotification));
await OnMessageSent(new MessageSentEventArgs(await t, o)); var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -336,20 +293,15 @@ public class DeviceSession : IDeviceSession
text = text.MarkdownV2Escape(); text = text.MarkdownV2Escape();
} }
try
{
var t = Api(a => a.SendTextMessageAsync(DeviceId, text, null, parseMode, replyToMessageId: replyTo,
replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendTextMessageAsync(DeviceId, text, null, parseMode, replyToMessageId: replyTo,
await OnMessageSent(new MessageSentEventArgs(await t, o)); replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -360,7 +312,7 @@ public class DeviceSession : IDeviceSession
/// <param name="replyTo"></param> /// <param name="replyTo"></param>
/// <param name="disableNotification"></param> /// <param name="disableNotification"></param>
/// <returns></returns> /// <returns></returns>
public async Task<Message> Send(string text, ReplyMarkupBase markup, int replyTo = 0, public async Task<Message> Send(string text, IReplyMarkup markup, int replyTo = 0,
bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown, bool disableNotification = false, ParseMode parseMode = ParseMode.Markdown,
bool markdownV2AutoEscape = true) bool markdownV2AutoEscape = true)
{ {
@@ -379,20 +331,15 @@ public class DeviceSession : IDeviceSession
text = text.MarkdownV2Escape(); text = text.MarkdownV2Escape();
} }
try
{
var t = Api(a => a.SendTextMessageAsync(DeviceId, text, null, parseMode, replyToMessageId: replyTo,
replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendTextMessageAsync(DeviceId, text, null, parseMode, replyToMessageId: replyTo,
await OnMessageSent(new MessageSentEventArgs(await t, o)); replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -414,20 +361,15 @@ public class DeviceSession : IDeviceSession
InlineKeyboardMarkup markup = buttons; InlineKeyboardMarkup markup = buttons;
try
{
var t = Api(a => a.SendPhotoAsync(DeviceId, file, null, caption, parseMode, replyToMessageId: replyTo,
replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendPhotoAsync(DeviceId, file, null, caption, parseMode, replyToMessageId: replyTo,
await OnMessageSent(new MessageSentEventArgs(await t, o)); replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -449,21 +391,16 @@ public class DeviceSession : IDeviceSession
InlineKeyboardMarkup markup = buttons; InlineKeyboardMarkup markup = buttons;
try
{
var t = Api(a => a.SendVideoAsync(DeviceId, file, caption: caption, parseMode: parseMode,
replyToMessageId: replyTo, replyMarkup: markup,
disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendVideoAsync(DeviceId, file, caption: caption, parseMode: parseMode,
await OnMessageSent(new MessageSentEventArgs(await t, o)); replyToMessageId: replyTo, replyMarkup: markup,
disableNotification: disableNotification));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -484,21 +421,16 @@ public class DeviceSession : IDeviceSession
InlineKeyboardMarkup markup = buttons; InlineKeyboardMarkup markup = buttons;
try
{
var t = Api(a => a.SendVideoAsync(DeviceId, InputFile.FromUri(url), parseMode: parseMode,
replyToMessageId: replyTo, replyMarkup: markup,
disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendVideoAsync(DeviceId, InputFile.FromUri(url), parseMode: parseMode,
await OnMessageSent(new MessageSentEventArgs(await t, o)); replyToMessageId: replyTo, replyMarkup: markup,
disableNotification: disableNotification));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -520,24 +452,19 @@ public class DeviceSession : IDeviceSession
InlineKeyboardMarkup markup = buttons; InlineKeyboardMarkup markup = buttons;
try
{
var ms = new MemoryStream(video);
var fts = InputFile.FromStream(ms, filename); var ms = new MemoryStream(video);
var t = Api(a => a.SendVideoAsync(DeviceId, fts, parseMode: parseMode, replyToMessageId: replyTo, var fts = InputFile.FromStream(ms, filename);
replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendVideoAsync(DeviceId, fts, parseMode: parseMode, replyToMessageId: replyTo,
await OnMessageSent(new MessageSentEventArgs(await t, o)); replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -560,26 +487,21 @@ public class DeviceSession : IDeviceSession
InlineKeyboardMarkup markup = buttons; InlineKeyboardMarkup markup = buttons;
try
{
var fs = new FileStream(filepath, FileMode.Open);
var filename = Path.GetFileName(filepath); var fs = new FileStream(filepath, FileMode.Open);
var fts = InputFile.FromStream(fs, filename); var filename = Path.GetFileName(filepath);
var t = Api(a => a.SendVideoAsync(DeviceId, fts, parseMode: parseMode, replyToMessageId: replyTo, var fts = InputFile.FromStream(fs, filename);
replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendVideoAsync(DeviceId, fts, parseMode: parseMode, replyToMessageId: replyTo,
await OnMessageSent(new MessageSentEventArgs(await t, o)); replyMarkup: markup, disableNotification: disableNotification));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -650,20 +572,15 @@ public class DeviceSession : IDeviceSession
markup = buttons; markup = buttons;
} }
try
{
var t = Api(a => a.SendDocumentAsync(DeviceId, document, null, null, caption, replyMarkup: markup,
disableNotification: disableNotification, replyToMessageId: replyTo));
var o = GetOrigin(new StackTrace()); var t = Api(a => a.SendDocumentAsync(DeviceId, document, null, null, caption, replyMarkup: markup,
await OnMessageSent(new MessageSentEventArgs(await t, o)); disableNotification: disableNotification, replyToMessageId: replyTo));
var o = GetOrigin(new StackTrace());
await OnMessageSent(new MessageSentEventArgs(await t, o));
return await t;
return await t;
}
catch
{
return null;
}
} }
/// <summary> /// <summary>
@@ -714,22 +631,16 @@ public class DeviceSession : IDeviceSession
public async Task<Message> HideReplyKeyboard(string closedMsg = "Closed", bool autoDeleteResponse = true) public async Task<Message> HideReplyKeyboard(string closedMsg = "Closed", bool autoDeleteResponse = true)
{ {
try
{
var m = await Send(closedMsg, new ReplyKeyboardRemove());
if (autoDeleteResponse && m != null) var m = await Send(closedMsg, new ReplyKeyboardRemove());
{
await DeleteMessage(m);
}
return m; if (autoDeleteResponse && m != null)
}
catch
{ {
await DeleteMessage(m);
} }
return null; return m;
} }
/// <summary> /// <summary>
@@ -759,13 +670,9 @@ public class DeviceSession : IDeviceSession
public virtual async Task ChangeChatPermissions(ChatPermissions permissions) public virtual async Task ChangeChatPermissions(ChatPermissions permissions)
{ {
try
{ await Api(a => a.SetChatPermissionsAsync(DeviceId, permissions));
await Api(a => a.SetChatPermissionsAsync(DeviceId, permissions));
}
catch
{
}
} }
private Type GetOrigin(StackTrace stackTrace) private Type GetOrigin(StackTrace stackTrace)
@@ -878,49 +785,31 @@ public class DeviceSession : IDeviceSession
public virtual async Task<ChatMember> GetChatUser(long userId) public virtual async Task<ChatMember> GetChatUser(long userId)
{ {
try
{
return await Api(a => a.GetChatMemberAsync(DeviceId, userId));
}
catch
{
}
return null; return await Api(a => a.GetChatMemberAsync(DeviceId, userId));
} }
[Obsolete("User BanUser instead.")] [Obsolete("User BanUser instead.")]
public virtual async Task KickUser(long userId, DateTime until = default) public virtual async Task KickUser(long userId, DateTime until = default)
{ {
try
{ await Api(a => a.BanChatMemberAsync(DeviceId, userId, until));
await Api(a => a.BanChatMemberAsync(DeviceId, userId, until));
}
catch
{
}
} }
public virtual async Task BanUser(long userId, DateTime until = default) public virtual async Task BanUser(long userId, DateTime until = default)
{ {
try
{ await Api(a => a.BanChatMemberAsync(DeviceId, userId, until));
await Api(a => a.BanChatMemberAsync(DeviceId, userId, until));
}
catch
{
}
} }
public virtual async Task UnbanUser(long userId) public virtual async Task UnbanUser(long userId)
{ {
try
{ await Api(a => a.UnbanChatMemberAsync(DeviceId, userId));
await Api(a => a.UnbanChatMemberAsync(DeviceId, userId));
}
catch
{
}
} }
#endregion #endregion
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace TelegramBotBase.States.Converter
{
public class DictionaryObjectJsonConverter : JsonConverter<Dictionary<string, object>>
{
public override Dictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dictionary = new Dictionary<string, object>();
var jsonDocument = JsonDocument.ParseValue(ref reader);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
switch (element.Value.ValueKind)
{
case JsonValueKind.String:
dictionary[element.Name] = element.Value.GetString();
break;
case JsonValueKind.Number:
if (element.Value.TryGetInt32(out var number))
{
dictionary[element.Name] = number;
continue;
}
if (element.Value.TryGetInt64(out long l))
dictionary[element.Name] = l;
else
dictionary[element.Name] = element.Value.GetDouble();
break;
case JsonValueKind.True:
case JsonValueKind.False:
dictionary[element.Name] = element.Value.GetBoolean();
break;
case JsonValueKind.Object:
dictionary[element.Name] = JsonSerializer.Deserialize<Dictionary<string, object>>(element.Value.GetRawText(), options);
break;
case JsonValueKind.Array:
dictionary[element.Name] = HandleArray(element.Value);
break;
default:
dictionary[element.Name] = element.Value.GetRawText();
break;
}
}
return dictionary;
}
private object HandleArray(JsonElement jsonArray)
{
// Hier wird geprüft, ob alle Elemente einer bestimmten Art angehören (z. B. int, string)
if (jsonArray.GetArrayLength() > 0)
{
var firstElement = jsonArray[0];
switch (firstElement.ValueKind)
{
case JsonValueKind.Number:
// Prüfen, ob alle Elemente ganze Zahlen sind
var isIntArray = true;
var isLongArray = false;
foreach (var element in jsonArray.EnumerateArray())
{
if (!element.TryGetInt32(out _))
{
isIntArray = false;
isLongArray = true;
if (!element.TryGetInt64(out _))
{
isLongArray = false;
break;
}
}
}
if (isIntArray)
{
var list = new List<int>();
foreach (var element in jsonArray.EnumerateArray())
{
list.Add(element.GetInt32());
}
return list;
}
else if (isLongArray)
{
var list = new List<long>();
foreach (var element in jsonArray.EnumerateArray())
{
list.Add(element.GetInt64());
}
return list;
}
else
{
var list = new List<double>();
foreach (var element in jsonArray.EnumerateArray())
{
list.Add(element.GetDouble());
}
return list;
}
case JsonValueKind.String:
var stringList = new List<string>();
foreach (var element in jsonArray.EnumerateArray())
{
stringList.Add(element.GetString());
}
return stringList;
case JsonValueKind.True:
case JsonValueKind.False:
var boolList = new List<bool>();
foreach (var element in jsonArray.EnumerateArray())
{
boolList.Add(element.GetBoolean());
}
return boolList;
default:
// Fallback: Liste von Objekten (z. B. wenn es sich um komplexe Objekte handelt)
var objectList = new List<object>();
foreach (var element in jsonArray.EnumerateArray())
{
objectList.Add(JsonSerializer.Deserialize<object>(element.GetRawText()));
}
return objectList;
}
}
// Leeres Array als Liste von Objekten zurückgeben
return new List<object>();
}
public override void Write(Utf8JsonWriter writer, Dictionary<string, object> value, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach (var kvp in value)
{
writer.WritePropertyName(kvp.Key);
JsonSerializer.Serialize(writer, kvp.Value, kvp.Value?.GetType() ?? typeof(object), options);
}
writer.WriteEndObject();
}
}
}
@@ -0,0 +1,296 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace TelegramBotBase.States.Converter
{
public class JsonTypeConverter : JsonConverter<object>
{
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument doc = JsonDocument.ParseValue(ref reader))
{
return ParseElement(doc.RootElement, options, typeof(object));
}
}
private object ParseElement(JsonElement element, JsonSerializerOptions options, Type expected_type)
{
// Wenn das Element ein JSON-Objekt ist
if (element.ValueKind == JsonValueKind.Object)
{
// Wenn es einen $type enthält, dann den Typ dynamisch instanziieren
if (element.TryGetProperty("$type", out JsonElement typeElement))
{
string typeName = typeElement.GetString();
Type type = Type.GetType(typeName);
if (type == null)
throw new InvalidOperationException($"Typ '{typeName}' konnte nicht gefunden werden.");
// Liste verarbeiten, falls $values enthalten ist
if (element.TryGetProperty("$values", out JsonElement valuesElement))
{
return ParseArray(valuesElement, type);
}
else if (expected_type.IsGenericType && expected_type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
var dictionary = new Dictionary<string, object>();
foreach (var property in element.EnumerateObject())
{
dictionary[property.Name] = ParseElement(property.Value, options, typeof(object));
}
return dictionary;
}
else
{
var instance = type.GetConstructor(new Type[] { }).Invoke(new object[] { });
var properties = type.GetProperties();
foreach (var p in properties)
{
if (!element.TryGetProperty(p.Name, out JsonElement value))
continue;
switch (value.ValueKind)
{
case JsonValueKind.Number:
if (p.PropertyType == typeof(int))
{
if (value.TryGetInt32(out int i))
{
p.SetValue(instance, i);
}
}
else if (p.PropertyType == typeof(long))
{
if (value.TryGetInt64(out long l))
{
p.SetValue(instance, l);
}
}
break;
case JsonValueKind.String:
p.SetValue(instance, value.ToString());
break;
default:
//if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
//{
// var dictionary = new Dictionary<string, object>();
// foreach (var property in element.EnumerateObject())
// {
// dictionary[property.Name] = ParseElement(property.Value, options, p.PropertyType);
// }
// p.SetValue(instance, dictionary);
//}
//else
//{
var obj = ParseElement(value, options, p.PropertyType);
if (obj == null)
continue;
try
{
p.SetValue(instance, obj);
}
catch
{
}
//}
break;
}
}
return instance;
// Objekt dynamisch deserialisieren
//return JsonSerializer.Deserialize(element.GetRawText(), type, options);
}
}
else
{
// Falls kein $type vorhanden ist, als Dictionary verarbeiten
var dictionary = new Dictionary<string, object>();
foreach (var property in element.EnumerateObject())
{
dictionary[property.Name] = ParseElement(property.Value, options, typeof(object));
}
return dictionary;
}
}
// Wenn das Element ein Array ist
else if (element.ValueKind == JsonValueKind.Array)
{
return ParseArray(element, typeof(object));
}
// Primitive Typen (String, Number, Bool)
else if (element.ValueKind == JsonValueKind.Number)
{
if (expected_type == typeof(Int64))
{
if (element.TryGetInt64(out long l))
return l;
}
else if (expected_type == typeof(Int32))
{
if (element.TryGetInt32(out int i))
return i;
}
if (element.TryGetInt32(out int i2))
return i2;
return 0;
}
else if (element.ValueKind == JsonValueKind.String)
{
return element.ToString();
}
else
{
#if NETCOREAPP3_1
return JsonSerializer.Deserialize(element.ToString(), typeof(object), options);
#else
return element.Deserialize<object>(options);
#endif
}
}
private object ParseElement(JsonElement element)
{
return null;
}
private object ParseArray(JsonElement arrayElement, Type listType)
{
// Wenn der Typ eine generische Liste ist
if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>))
{
Type elementType = listType.GetGenericArguments()[0];
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
foreach (var item in arrayElement.EnumerateArray())
{
list.Add(ParseElement(item, new JsonSerializerOptions(), elementType));
}
return list;
}
else
{
// Standardmäßig als List<object>
var list = new List<object>();
foreach (var item in arrayElement.EnumerateArray())
{
list.Add(ParseElement(item, new JsonSerializerOptions(), typeof(object)));
}
return list;
}
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
WriteWithTypeInfo(writer, value, options);
}
private void WriteWithTypeInfo(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
Type type = value.GetType();
// Primitives und grundlegende Typen (int, string, bool, etc.)
if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
{
JsonSerializer.Serialize(writer, value, type, options);
}
// Behandlung für Sammlungen
else if (value is IList list)
{
writer.WriteStartObject();
writer.WriteString("$type", type.AssemblyQualifiedName);
writer.WritePropertyName("$values");
writer.WriteStartArray();
foreach (var item in list)
{
WriteWithTypeInfo(writer, item, options);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
// Behandlung für Dictionaries
else if (value is IDictionary dictionary)
{
writer.WriteStartObject();
writer.WriteString("$type", type.AssemblyQualifiedName);
foreach (DictionaryEntry entry in dictionary)
{
writer.WritePropertyName(entry.Key.ToString());
WriteWithTypeInfo(writer, entry.Value, options);
}
writer.WriteEndObject();
}
// Komplexe Objekte
else if (value is Enum e)
{
int eValue = (int)value;
writer.WriteNumberValue(eValue);
}
else
{
writer.WriteStartObject();
writer.WriteString("$type", type.AssemblyQualifiedName);
foreach (var property in type.GetProperties())
{
if (property.CanRead)
{
var propValue = property.GetValue(value);
writer.WritePropertyName(property.Name);
WriteWithTypeInfo(writer, propValue, options);
}
}
writer.WriteEndObject();
}
}
}
}
+26 -11
View File
@@ -1,6 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using Newtonsoft.Json; using System.Text.Json;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Form; using TelegramBotBase.Form;
@@ -29,7 +29,7 @@ public class JsonStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase))) if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{ {
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase"); throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
} }
FilePath = file ?? throw new ArgumentNullException(nameof(file)); FilePath = file ?? throw new ArgumentNullException(nameof(file));
@@ -48,16 +48,24 @@ public class JsonStateMachine : IStateMachine
{ {
var content = File.ReadAllText(FilePath); var content = File.ReadAllText(FilePath);
var sc = JsonConvert.DeserializeObject<StateContainer>(content, new JsonSerializerSettings var options = new JsonSerializerOptions
{ {
TypeNameHandling = TypeNameHandling.All, Converters = {
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple new Converter.JsonTypeConverter(),
}); //new Converter.DictionaryObjectJsonConverter(),
},
PropertyNameCaseInsensitive = true
};
var obj = JsonSerializer.Deserialize<object>(content, options) ;
var sc = obj as StateContainer;
return sc; return sc;
} }
catch catch(Exception ex)
{ {
throw ex;
} }
return new StateContainer(); return new StateContainer();
@@ -77,16 +85,23 @@ public class JsonStateMachine : IStateMachine
try try
{ {
var content = JsonConvert.SerializeObject(e.States, Formatting.Indented, new JsonSerializerSettings var options = new JsonSerializerOptions
{ {
TypeNameHandling = TypeNameHandling.All, WriteIndented = true,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple Converters = {
}); new Converter.JsonTypeConverter(),
},
PropertyNameCaseInsensitive = true
};
var content = JsonSerializer.Serialize<object>(e.States, options);
File.WriteAllText(FilePath, content); File.WriteAllText(FilePath, content);
} }
catch catch
{ {
} }
} }
} }
@@ -1,6 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using Newtonsoft.Json; using System.Text.Json;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Form; using TelegramBotBase.Form;
@@ -30,7 +30,7 @@ public class SimpleJsonStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase))) if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{ {
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase"); throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
} }
FilePath = file ?? throw new ArgumentNullException(nameof(file)); FilePath = file ?? throw new ArgumentNullException(nameof(file));
@@ -49,7 +49,15 @@ public class SimpleJsonStateMachine : IStateMachine
{ {
var content = File.ReadAllText(FilePath); var content = File.ReadAllText(FilePath);
var sc = JsonConvert.DeserializeObject<StateContainer>(content); var options = new JsonSerializerOptions
{
Converters = {
new Converter.DictionaryObjectJsonConverter(),
new Converter.JsonTypeConverter()
}
};
var sc = JsonSerializer.Deserialize<StateContainer>(content, options);
return sc; return sc;
} }
@@ -74,7 +82,16 @@ public class SimpleJsonStateMachine : IStateMachine
try try
{ {
var content = JsonConvert.SerializeObject(e.States, Formatting.Indented); var options = new JsonSerializerOptions
{
WriteIndented = true,
Converters = {
new Converter.DictionaryObjectJsonConverter(),
new Converter.JsonTypeConverter()
}
};
var content = JsonSerializer.Serialize(e.States, options);
File.WriteAllText(FilePath, content); File.WriteAllText(FilePath, content);
} }
+1 -1
View File
@@ -27,7 +27,7 @@ public class XmlStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase))) if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{ {
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase"); throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
} }
FilePath = file ?? throw new ArgumentNullException(nameof(file)); FilePath = file ?? throw new ArgumentNullException(nameof(file));
+8 -3
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6</TargetFrameworks> <TargetFrameworks>netcoreapp3.1;net6;net7;net8</TargetFrameworks>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -22,8 +22,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
@@ -60,4 +60,9 @@
<PackageReference Include="Telegram.Bot" Version="19.0.0" /> <PackageReference Include="Telegram.Bot" Version="19.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase.SourceGenerators\TelegramBotBase.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" TargetFramework="netstandard2.0" />
</ItemGroup>
</Project> </Project>
-2
View File
@@ -14,12 +14,10 @@
<dependencies> <dependencies>
<group targetFramework=".NETFramework4.6.1"> <group targetFramework=".NETFramework4.6.1">
<dependency id="Newtonsoft.Json" version="13.0.1" exclude="Build,Analyzers" /> <dependency id="Newtonsoft.Json" version="13.0.1" exclude="Build,Analyzers" />
<dependency id="System.Drawing.Common" version="4.6.0" exclude="Build,Analyzers" />
<dependency id="Telegram.Bot" version="19.0.0" exclude="Build,Analyzers" /> <dependency id="Telegram.Bot" version="19.0.0" exclude="Build,Analyzers" />
</group> </group>
<group targetFramework=".NETStandard2.0"> <group targetFramework=".NETStandard2.0">
<dependency id="Newtonsoft.Json" version="13.0.1" exclude="Build,Analyzers" /> <dependency id="Newtonsoft.Json" version="13.0.1" exclude="Build,Analyzers" />
<dependency id="System.Drawing.Common" version="4.6.0" exclude="Build,Analyzers" />
<dependency id="Telegram.Bot" version="19.0.0" exclude="Build,Analyzers" /> <dependency id="Telegram.Bot" version="19.0.0" exclude="Build,Analyzers" />
</group> </group>
</dependencies> </dependencies>
+35 -1
View File
@@ -30,7 +30,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCoreBot", "Examples\EFCor
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotAndWebApplication", "Examples\BotAndWebApplication\BotAndWebApplication.csproj", "{52EA3201-02E8-46F5-87C4-B4752C8A815C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotAndWebApplication", "Examples\BotAndWebApplication\BotAndWebApplication.csproj", "{52EA3201-02E8-46F5-87C4-B4752C8A815C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "Examples\DependencyInjection\DependencyInjection.csproj", "{689B16BC-200E-4C68-BB2E-8B209070849B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramBotBase.Extensions.Serializer.Database.PostgreSql", "TelegramBotBase.Extensions.Serializer.Database.PostgreSql\TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj", "{7C55D9FF-7DC1-41D0-809C-469EBFA20992}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramBotBase.Extensions.Images.IronSoftware", "TelegramBotBase.Extensions.Images.IronSoftware\TelegramBotBase.Extensions.Images.IronSoftware.csproj", "{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramBotBase.SourceGenerators", "TelegramBotBase.SourceGenerators\TelegramBotBase.SourceGenerators.csproj", "{B78455D6-8AF2-459C-B56A-210DC89D7793}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson", "TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson\TelegramBotBase.Extensions.Serializer.Legacy.NewtonsoftJson.csproj", "{21C44B20-1ED8-4BE6-A629-C2EC737C06E0}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -78,6 +88,26 @@ Global
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Debug|Any CPU.Build.0 = Debug|Any CPU {067E8EBE-F90A-4AFF-A0FF-20578216486E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.ActiveCfg = Release|Any CPU {067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.Build.0 = Release|Any CPU {067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.Build.0 = Release|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.Build.0 = Release|Any CPU
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Release|Any CPU.Build.0 = Release|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Release|Any CPU.Build.0 = Release|Any CPU
{B78455D6-8AF2-459C-B56A-210DC89D7793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B78455D6-8AF2-459C-B56A-210DC89D7793}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B78455D6-8AF2-459C-B56A-210DC89D7793}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B78455D6-8AF2-459C-B56A-210DC89D7793}.Release|Any CPU.Build.0 = Release|Any CPU
{21C44B20-1ED8-4BE6-A629-C2EC737C06E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21C44B20-1ED8-4BE6-A629-C2EC737C06E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21C44B20-1ED8-4BE6-A629-C2EC737C06E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21C44B20-1ED8-4BE6-A629-C2EC737C06E0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -91,6 +121,10 @@ Global
{261BED47-0404-4A9A-86FC-047DE42A7D25} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5} {261BED47-0404-4A9A-86FC-047DE42A7D25} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{52EA3201-02E8-46F5-87C4-B4752C8A815C} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5} {52EA3201-02E8-46F5-87C4-B4752C8A815C} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{067E8EBE-F90A-4AFF-A0FF-20578216486E} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5} {067E8EBE-F90A-4AFF-A0FF-20578216486E} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{689B16BC-200E-4C68-BB2E-8B209070849B} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{7C55D9FF-7DC1-41D0-809C-469EBFA20992} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
{21C44B20-1ED8-4BE6-A629-C2EC737C06E0} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {59CB40E1-9FA7-4867-A56F-4F418286F057} SolutionGuid = {59CB40E1-9FA7-4867-A56F-4F418286F057}